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

[经验分享] memcache(三)内存管理

[复制链接]

尚未签到

发表于 2015-8-31 08:41:02 | 显示全部楼层 |阅读模式
memcached(三)内存管理
  memcached使用预申请的方式来管理内存的分配,从而避免内存碎片化的问题。如果采用mallo和free来动态的申请和销毁内存,必然会产生大量的内存碎片。

基本知识
  slab:内存块是memcached一次申请内存的最小单元,在memcached中一个slab的默认大小为1M;
  slabclass:特定大小的chunk的组。
  chunk:缓存的内存空间,一个slab被划分为若干个chunk;
  item:存储数据的最小单元,每一个chunk都会包含一个item;
  factor:增长因子,默认为1.25,相邻slab中的item大小与factor成比例关系;

基本原理
  memcached使用预分配方法,避免频繁的调用malloc和free;
  memcached通过不同的slab来管理不同chunk大小的内存块,从而满足存储不同大小的数据。
  slab的申请是通过在使用item时申请slab大小的内存空间,然后再把内存切割为大小相同的item,挂在到slab的未使用链表上。
  过期和被删除item并不会被free掉,memcached并不会删除已经分配的内存;
  Memcached会优先使用已超时的记录空间,通过LRU算法;
  memcached使用lazy expiration来判断元素是否过期,所以过期监视上不会占用cpu时间。

源码分析
  下面主要分析memcached的内存申请和存储相关代码。

item
  item是key/value的存储单元。



typedef struct _stritem {
struct _stritem *next;      /* 前后指针用于在链表slab->slots中连接前后数据 */
struct _stritem *prev;
struct _stritem *h_next;    /* hash chain next */
rel_time_t      time;       /* 最后一次访问时间 */
rel_time_t      exptime;    /* 过期时间 */
int             nbytes;     /* 数据大小 */
unsigned short  refcount;   /* 引用次数 */
uint8_t         nsuffix;    /* suffix长度 */
uint8_t         it_flags;   /* ITEM_* above */
uint8_t         slabs_clsid;/* 所有slab的id */
uint8_t         nkey;       /* key长度 */
/* 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[]; /* cas|key|suffix|value */
} item;
slab初始化



void slabs_init(const size_t limit, const double factor, const bool prealloc) {
int i = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size; /* 得到每一个item的大小 */
mem_limit = limit;
if (prealloc) { /* 预分配一块内存 */
...
}
memset(slabclass, 0, sizeof(slabclass)); /* 把slabclass置为0,slabclass是一个slab数组,存储所有slab的信息 */
while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {  /* 循环初始化每一个slab的内容,保证slab中item的size小于max_size/factor */
/* Make sure items are always n-byte aligned */
if (size % CHUNK_ALIGN_BYTES)  /* 用于内存对齐 */
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
slabclass.size = size; /* 初始化slabclass中item的大小 */
slabclass.perslab = settings.item_size_max / slabclass.size; /* 初始化每个slab中item的数量 */
size *= factor;  /* item的大小随factor逐渐增大 */
...
}
/* 初始化最后一个slab,大小为最大的max_size,只有一个item */
power_largest = i;
slabclass[power_largest].size = settings.item_size_max;
slabclass[power_largest].perslab = 1;
...
}
  从源码中,可以看出来同一个slab中所有的item的大小都是固定的,

申请slab内存



static void *do_slabs_alloc(const size_t size, unsigned int id) {
slabclass_t *p;
void *ret = NULL;
item *it = NULL;
if (id < POWER_SMALLEST || id > power_largest) { /* 判断id是否合法 */
MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);
return NULL;
}
p = &slabclass[id]; /* 获取slab */
assert(p->sl_curr == 0 || ((item *)p->slots)->slabs_clsid == 0);
/* fail unless we have space at the end of a recently allocated page,
we have something on our freelist, or we could allocate a new page */
if (! (p->sl_curr != 0 || do_slabs_newslab(id) != 0)) { /*如果sl_curr为0,没有剩余的item,那么就执行do_slabs_newslab申请内存空间*/
/* We don't have more memory available */
ret = NULL;
} else if (p->sl_curr != 0) { /* 如果有未使用的空间,则获取该item,并从slots链表中删除该item */
/* return off our freelist */
it = (item *)p->slots;
p->slots = it->next;
if (it->next) it->next->prev = 0;
p->sl_curr--;
ret = (void *)it;
}
...
return ret;
}
  sl_curr来判断是否存在未使用的内容空间,如果不存在需要调用do_slabs_newslab来申请slab空间。



static int do_slabs_newslab(const unsigned int id) {
slabclass_t *p = &slabclass[id];
int len = settings.slab_reassign ? settings.item_size_max
: p->size * p->perslab;
char *ptr;
/* 1. 判断是否超过内存限制
2. 判断是否申请过内存空间
3. 如果没有申请过,则申请slab->size*slab->perslab大小的整块内存
4.如果申请过,调用grow_slab_list来扩大slab大小 */
if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) ||
(grow_slab_list(id) == 0) ||
((ptr = memory_allocate((size_t)len)) == 0)) {
MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
}
memset(ptr, 0, (size_t)len);
split_slab_page_into_freelist(ptr, id); /* 把申请的内存分配到slots链表中 */
p->slab_list[p->slabs++] = ptr;
mem_malloced += len;
MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);
return 1;
}
  申请空间后,需要通过split_slab_page_into_freelist函数把申请的内存空间分配到未使用的链表中。



static void split_slab_page_into_freelist(char *ptr, const unsigned int id) {
slabclass_t *p = &slabclass[id];
int x;
for (x = 0; x < p->perslab; x++) { /* 循环分配内存 */
do_slabs_free(ptr, 0, id);
ptr += p->size;
}
}
static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
slabclass_t *p;
item *it;
...
p = &slabclass[id];
/* 获取内存指针,把item块挂在到slots链表中,增加sl_curr */
it = (item *)ptr;
it->it_flags |= ITEM_SLABBED;
it->prev = 0;
it->next = p->slots;
if (it->next) it->next->prev = it;
p->slots = it;
p->sl_curr++;
p->requested -= size;
return;
}
获取适当大小的item
  在do_item_alloc中,调用了slabs_clsid来获取适合存储当前元素的slab id。



unsigned int slabs_clsid(const size_t size) {
int res = POWER_SMALLEST;
if (size == 0)
return 0;
while (size > slabclass[res].size)    /* 遍历slabclass来找到适合size的item */
if (res++ == power_largest)     /* won't fit in the biggest slab */
return 0;
return res;
}
优缺点
  内存预分配可以避免内存碎片以及避免动态分配造成的开销。
  内存分配是由冗余的,当一个slab不能被它所拥有的chunk大小整除时,slab尾部剩余的空间就会被丢弃。
  由于分配的是特定长度的内存,因此无法有效地利用所有分配的内存,例如如果将100字节的数据存储在128字节的chunk中,会造成28字节的浪费。
  

运维网声明 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-106555-1-1.html 上篇帖子: memcache命令 下篇帖子: .NET JAVA PHP中写入数据及读取memcache数据不一致问题
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

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

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

扫描微信二维码查看详情

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


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


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


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



合作伙伴: 青云cloud

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