设为首页 收藏本站
查看: 1426|回复: 0

[经验分享] [原]Memcached源码剖析系列之内存存储机制(一)

[复制链接]

尚未签到

发表于 2015-8-31 13:20:11 | 显示全部楼层 |阅读模式
   一 内存分配管理机制
    memcached是一个高性能的,分布式内存对象缓存系统,用于在动态系统中减少数据库负载,提升性能。memcached有一个很有特色的内存管理方式,为了提高效率,默认情况下采用了名为Slab Allocator的机制分配管理内存空间。   

    memcached文档中关于slab allocator有这么一段话:

    the primary goal of the slabs subsystem in memcached was to eliminate memory fragmentation issues totally by using fixed-size memory chunks coming from a few predetermined size classes.

    由此,我们可以看出,memcached使用预申请内存并分组成特定块的方式,旨在解决内存碎片的问题。

    Memcached的内存管理方式还是比较简单易懂的,使用的是slab->chunk的组织方式管理内存。Slab是Memcached进行内存申请的最小单位,默认一般为1MB,可使用命令行参数进行自定义设置。然后使用分块机制将slab分成一定大小分成若干个chunks。如下图所示(此图来源于网络):


DSC0000.png

二 源码分析     

1 关键数据结构

(1)settings结构体原型:


/* When adding a setting, be sure to update process_stat_settings */
/**
* Globally accessible settings as derived from the commandline.
*/
struct settings {
//最大内存, 默认64M,最大2G。通过-m 设定
size_t maxbytes;
//最大连接数,默认1024 通过-c设定
int maxconns;
//tcp 端口号,通过-p 设置
int port;
//ucp 端口号,通过-U 设置
int udpport;
//监听IP或SOCKET地址 ,通过-l设定
char *inter;
//是否输出debug信息。由-v,-vvv参数设定
int verbose;
//时间设定,当使用flsuh时,只需要修改本值,当取出的值时间小于本值时,将被忽略。
rel_time_t oldest_live; /* ignore existing items older than this */
//当内存存满时,是否淘汰老数据。默认是是。可用-M修改为否。此时内容耗尽时,新插入数据时将返回失败。
int evict_to_free;
//socket模式,使用-s设定。
char *socketpath;   /* path to unix socket if using local socket */
//socket文件的文件权限,使用-a设定
int access;  /* access mask (a la chmod) for unix domain socket */
//slab分配增量因子,默认围1.25, 可通过-f设定
double factor;          /* chunk size growth factor */
//给一个key+value+flags 分配的最小字节数。 默认值为48. 可通过-n修改。
int chunk_size;
//工作线程数。默认围4, 可通过-t设定
int num_threads;        /* number of worker (without dispatcher) libevent threads to run */
//状态详情的key前缀
char prefix_delimiter;  /* character that marks a key prefix (for stats) */
//开启状态详情记录
int detail_enabled;     /* nonzero if we're collecting detailed stats */
//每个event处理的请求数
int reqs_per_event;     /* Maximum number of io to process on each  io-event. */
//开启cas,"cas"是一个存储检查操作。用来检查脏数据的存操作。在取出数据后,如果没有其他人修改此数据时,本进程才能够存储数据。默认为开启。需要版本:1.3+
bool use_cas;
//使用协议, 试过-B参数设定。 可能值为:ascii, binary, or auto, 版本: 1.4.0+
enum protocol binding_protocol;
//等待处理的排队队列长度。默认值为1024.
int backlog;
//单个item最大字计数。默认1M。可通过-I参数修改。在1.4.2版本之后,这个值可以大于1M,必须小于128M。但memcached会抛出警告,大于1M将导致整体运行内存的增加和内存性能的降低。 版本: 1.4.2+
int item_size_max;        /* Maximum item size, and upper end for slabs */
//是否开启sasl
bool sasl;              /* SASL on/off */
};

(2)item结构体原型:


typedef struct _stritem {
struct _stritem *next;
struct _stritem *prev;
struct _stritem *h_next;    /* hash chain next */
rel_time_t      time;       /* least recent access */
rel_time_t      exptime;    /* expire time */
int             nbytes;     /* size of data */
unsigned short  refcount;
uint8_t         nsuffix;    /* length of flags-and-length string */
uint8_t         it_flags;   /* ITEM_* above */
uint8_t         slabs_clsid;/* which slab class we're in */
uint8_t         nkey;       /* key length, w/terminating null and padding */
/* this odd type prevents type-punning issues when we do
* the little shuffle to save space when not using CAS. */
union {
uint64_t cas;
char end;
} data[];
/* if it_flags & ITEM_CAS we have 8 bytes CAS */
/* then null-terminated key */
/* then " flags length\r\n" (no terminating null) */
/* then data with terminating \r\n (no terminating null; it's binary!) */
} item;

(3)slabclass_t结构体原型


typedef struct {
unsigned int size;      /* sizes of items */
unsigned int perslab;   /* how many items per slab */
void **slots;           /* list of item ptrs */
unsigned int sl_total;  /* size of previous array */
unsigned int sl_curr;   /* first free slot */
void *end_page_ptr;         /* pointer to next free item at end of page, or 0 */
unsigned int end_page_free; /* number of items remaining at end of last alloced page */
unsigned int slabs;     /* how many slabs were allocated for this class */
void **slab_list;       /* array of slab pointers */
unsigned int list_size; /* size of prev array */
unsigned int killing;  /* index+1 of dying slab, or zero if none */
size_t requested; /* The number of requested bytes */
} slabclass_t;

(4)memcatchd.c文件中定义的部分宏


#define POWER_SMALLEST 1
#define POWER_LARGEST  200
#define CHUNK_ALIGN_BYTES 8
#define DONT_PREALLOC_SLABS
#define MAX_NUMBER_OF_SLAB_CLASSES (POWER_LARGEST + 1)

2 分配算法的实现

(1)memcatchd.c中main函数中运行状态的初始化


int main()
{

settings_init();

//利用命令行参数信息,对setting进行设置
while (-1 != (c = getopt(argc, argv,…)
{…}

//settings.factor 初始化为1.25,可以使用命令行参数-f进行设置
slabs_init(settings.maxbytes, settings.factor, preallocate);
}
settings_init()是初始化全局变量settings函数,在memcatchd.c文件实现


static void settings_init(void) {
settings.use_cas = true;
settings.access = 0700;
settings.port = 11211;
settings.udpport = 11211;
/* By default this string should be NULL for getaddrinfo() */
settings.inter = NULL;
settings.maxbytes = 64 * 1024 * 1024; /* default is 64MB */
settings.maxconns = 1024;         /* to limit connections-related memory to about 5MB */
settings.verbose = 0;
settings.oldest_live = 0;
settings.evict_to_free = 1;       /* push old items out of cache when memory runs out */
settings.socketpath = NULL;       /* by default, not using a unix socket */
settings.factor = 1.25;
settings.chunk_size = 48;         /* space for a modest key and value */
settings.num_threads = 4;         /* N workers */
settings.num_threads_per_udp = 0;
settings.prefix_delimiter = ':';
settings.detail_enabled = 0;
settings.reqs_per_event = 20;
settings.backlog = 1024;
settings.binding_protocol = negotiating_prot;
settings.item_size_max = 1024 * 1024; /* The famous 1MB upper limit. */
}

    从该设置setting的初始化函数可看出,settings.item_size_max = 1024 * 1024; 即每个slab默认的空间大小为1MB,settings.factor = 1.25; 默认设置item的size步长增长因子为1.25。使用命令行参数对setting进行定制后,调用slabs_init函数,根据配置的setting来初始化slabclass。slabs_init函数于Slabs.c文件中实现:


// slabs管理器初始化函数:limit默认64MB,prealloc默认false,可使用命令行参数’L’进行设置。
void slabs_init(const size_t limit, const double factor, const bool prealloc) {
int i = POWER_SMALLEST - 1;//#define POWER_SMALLEST 1;i初始化为0
//item(_stritem):storing items within memcached
unsigned int size = sizeof(item) + settings.chunk_size;//chunk_size:48
mem_limit = limit;  //limit默认64MB
//预分配为真时:
if (prealloc) {
/* Allocate everything in a big chunk with malloc */
mem_base = malloc(mem_limit);
if (mem_base != NULL) {
//mem_current:静态变量,记录分配内存块的基地址
//mem_avail:可用内存大小
mem_current = mem_base;
mem_avail = mem_limit;
} else {
fprintf(stderr, "Warning: Failed to allocate requested memory in"
" one large chunk.\nWill allocate in smaller chunks\n");
}
}
//static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];
//#define MAX_NUMBER_OF_SLAB_CLASSES (POWER_LARGEST + 1)
//#define POWER_LARGEST  200
memset(slabclass, 0, sizeof(slabclass));
// /* settings.item_size_max: Maximum item size, and upper end for slabs,默认为1MB */
//item核心分配算法
while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
/* Make sure items are always n-byte aligned */
//#define CHUNK_ALIGN_BYTES 8
if (size % CHUNK_ALIGN_BYTES)    //确保size为CHUNK_ALIGN_BYTES的倍数,不够则向补足
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
slabclass.size = size;
slabclass.perslab = settings.item_size_max / slabclass.size;  //记录每个slab中item的个数
size *= factor;   //每次循环size的大小都增加factor倍
if (settings.verbose > 1) {
fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
i, slabclass.size, slabclass.perslab);
}
}
//补足一块大小为item_size_max的块
power_largest = i;
slabclass[power_largest].size = settings.item_size_max;
slabclass[power_largest].perslab = 1;
if (settings.verbose > 1) {
fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
i, slabclass.size, slabclass.perslab);
}
/* for the test suite:  faking of how much we've already malloc'd */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = (size_t)atol(t_initial_malloc);
}
}
#ifndef DONT_PREALLOC_SLABS  //已经定义了
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (pre_alloc == NULL || atoi(pre_alloc) != 0) {
slabs_preallocate(power_largest);
}
}
#endif
}
    在memcached的内存管理机制中,使用了一个slabclass_t类型(类型声明见上“关键数据结构”一节)的数组slabclass对划分的slab及进行统一的管理

slabclass的声明:static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];

    每一个slab被划分为若干个chunk,每个chunk里保存一个item,每个item同时包含了item结构体、key和value(注意在memcached中的value是只有字符串的)。slab按照自己的id分别组成链表,这些链表又按id挂在一个slabclass数组上,整个结构看起来有点像二维数组。

    在定位item时,使用slabs_clsid函数,传入参数为item大小,返回值为classid:


/*
* Figures out which slab class (chunk size) is required to store an item of
* a given size.
* Given object size, return id to use when allocating/freeing memory for object
* 0 means error: can't store such a large object
*/
unsigned int slabs_clsid(const size_t size) {
int res = POWER_SMALLEST;
if (size == 0)
return 0;
while (size > slabclass[res].size)
if (res++ == power_largest)     /* won't fit in the biggest slab */
return 0;  //分配的值不能满足
return res;  //返回第一个大于size的索引值
}
    根据返回的索引值即可定位到满足该size的slabclass项。从源码中可以看出:chunk的size初始值为sizeof(item)+settings.chunk_size(key 和 value所使用的最小空间,默认为48);chunk的大小以factor的倍数进行增长,最高为slab的最大值的一半,最后一个slab的大小为slab的最大值,这也是memcached所能允许分配的最大的item值。



本小节到此结束,在下一小节中将继续分析memcached的存储机制并分析该机制的优缺点。

注:本系列文章基于memcached-1.4.6版本进行分析。

reference:

[1] http://blog.developers.api.sina.com.cn/?p=124&cpage=1#comment-1506

[2] http://kb.cnblogs.com/page/42732/







作者:lgp88 发表于2012-5-14 14:47:54 原文链接

阅读:176 评论:0 查看评论  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-106836-1-1.html 上篇帖子: 利用Spring AOP 更新memcached 缓存策略的实现(一) 下篇帖子: MemCached配置与缓存知识概述
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表