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

[经验分享] 关于Memcache内存管理模型的理解

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-3-31 10:12:39 | 显示全部楼层 |阅读模式
说在前面
本文不包含为什么使用memcache,以及如何使用memcache等基础知识。相关知识请查阅各类手册。 另,为便于理解,最好手头准备一份memcache的源码,本文使用的是目前最新的1.4.4版本源码,可自行到github上clone。

Item、Chunk、Page、Slab
Data Item

+---------------------------------------+
|  key-value | cas | suffix | item head |  
+---------------------------------------+
Item指实际存放到memcache中的数据对象结构,除key-value数据外,还包括memcache自身对数据对象的描述信息(Item=key+value+后缀长+32byte结构体)

Chunk

Chunk指Memcache用来存放Data Item的最小单元,同一个Slab中的chunk大小是固定的。

+------------------------------+
|   data item    | empty space |
+------------------------------+
Page
+-------------------------------------+
|  chunk1 | chunk2 | chunk3 | chunk4  |
+-------------------------------------+
每个Slab中按照Page来申请内存,Page的大小默认为1M,可以通过-l参数调整,最小1k,最大128m.

Slab

+--------------------------------+
|  Page1 | Page2 | Page3 | Page4 |
+--------------------------------+
Memcache将分配给它的内存(-m 参数指定,默认64m)按照Chunk大小不同,划分为多个slab。

他们三者的关系如下图所示:

                 Chunk
                   ^                                                         
+------------------|------------------------------------------------------------+
|   Memory         |                                                            |
|  +---------------|---------------------------------------------------------+  |
|  |      +--------|---------------------+  +------------------------------+ |  |
|  |      |Page1 +-|---+ +-----+ +-----+ |  |Page2 +-----+ +-----+ +-----+ | |  |
|  | Slab |(1M)  | 96B | | 68B | | 72B | |  |(1M)  | 92B | | 76B | | 84B | | |  |
|  |  1   |      +-----+ +-----+ +-----+ |  |      +-----+ +-----+ +-----+ | |  |
|  |      +------------------------------+  +------------------------------+ |  |
|  +-------------------------------------------------------------------------+  |
|                                                                               |
|  +-------------------------------------------------------------------------+  |
|  |      +------------------------------+  +------------------------------+ |  |
|  |      |Page1 +------+    +------+    |  |Page2 +------+    +-------+   | |  |
|  | Slab | (1M) | 128B |    | 120B |    |  |(1M)  | 128B |    | 97B   |   | |  |
|  |   2  |      +------+    +------+    |  |      +------+    +-------+   | |  |
|  |      +------------------------------+  +------------------------------+ |  |
|  +-------------------------------------------------------------------------+  |
+-------------------------------------------------------------------------------+
Slab内存分配
slab初始化

Memcache启动时会进行slab初始化(参见slabs.c中slabs_init()函数),默认最小的chunksize为80(查看源码会发现settings中chunk_size默认为48,但是实际还需要加上一个32bytes的item结构体),可以通过-n参数调整,按照然后按照factor(默认为1.25,可以通过-f参数调整)(_关于参数更多的memcache默认参数可以参考memcache.c中settings的设置_)比例递增,划分出多个不同chunk大小的slab空间,即slab1的chunk大小=80,slab2的chunk大小为80*1.25=100,slab3的chunk大小为80*1.25*1.25=125,但最大一个一个chunk不会大于一个Page的大小(默认1M)。

一下代码节选自 slabs.c
95 void slabs_init(const size_t limit, const double factor, const bool prealloc) {
96     int i = POWER_SMALLEST - 1;
97     unsigned int size = sizeof(item) + settings.chunk_size;
98  
99     mem_limit = limit;
100  
101     if (prealloc) {
102         /* Allocate everything in a big chunk with malloc */
103         mem_base = malloc(mem_limit);
104         if (mem_base != NULL) {
105             mem_current = mem_base;
106             mem_avail = mem_limit;
107         } else {
108             fprintf(stderr, "Warning: Failed to allocate requested memory in"
109                     " one large chunk.\nWill allocate in smaller chunks\n");
110         }
111     }
112  
113     memset(slabclass, 0, sizeof(slabclass));
114  
115     while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
116         /* Make sure items are always n-byte aligned */
117         if (size % CHUNK_ALIGN_BYTES)
118             size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
119  
120         slabclass[i].size = size;
121         slabclass[i].perslab = settings.item_size_max / slabclass[i].size;
122         size *= factor;
123         if (settings.verbose > 1) {
124             fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
125                     i, slabclass[i].size, slabclass[i].perslab);
126         }
127     }
128  
129     power_largest = i;
130     slabclass[power_largest].size = settings.item_size_max;
131     slabclass[power_largest].perslab = 1;
132     if (settings.verbose > 1) {
133         fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
134                 i, slabclass[i].size, slabclass[i].perslab);
135     }
136  
137     /* for the test suite:  faking of how much we've already malloc'd */
138     {
139         char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
140         if (t_initial_malloc) {
141             mem_malloced = (size_t)atol(t_initial_malloc);
142         }
143  
144     }
145  
146     if (prealloc) {
147         slabs_preallocate(power_largest);
148     }
149 }                             
PS:prealloc指的是直接申请一个大的chunk存放所有数据,默认是不采用这种方式的。

数据存储过程

一个数据项的大致存储量过程可以理解为(完整代码较长,不在粘贴,具体可参见items.c中do_item_alloc()方法):

构造一个数据项结构体,计算数据项的大小,(假设默认配置下,数据项大小为102B)
根据数据项的大小,找到最合适的slab,(100<102<125,所以存储在slab3中)
检查该slab中是否有过期的数据,如有清理掉
如果没有过期的数据项,则从当前slab中申请空间,参见slabs.c中slab_alloc()方法。
如果当前slab中申请失败,则尝试根据LRU算法逐出一个数据项,默认memcache是允许逐出的,如果被设置为禁止逐出,那么这是会反生悲剧的oom了
获取到item空间后将数据存储到改空间中,并追加到该slab的item列表中
一个slab的申请一个chunk空间的过程大致如下(以下代码节选自slabs.c):

195 static int do_slabs_newslab(const unsigned int id) {
196     slabclass_t *p = &slabclass[id];
197     int len = settings.slab_reassign ? settings.item_size_max
198         : p->size * p->perslab;
199     char *ptr;            
200            
201     if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) ||
202         (grow_slab_list(id) == 0) ||   
203         ((ptr = memory_allocate((size_t)len)) == 0)) {
204            
205         MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
206         return 0;         
207     }      
208            
209     memset(ptr, 0, (size_t)len);   
210     split_slab_page_into_freelist(ptr, id);
211            
212     p->slab_list[p->slabs++] = ptr;
213     mem_malloced += len;   
214     MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);
215            
216     return 1;              
217 }
218  
219 /*@null@*/
220 static void *do_slabs_alloc(const size_t size, unsigned int id) {
221     slabclass_t *p;        
222     void *ret = NULL;      
223     item *it = NULL;      
224  
225     if (id < POWER_SMALLEST || id > power_largest) {
226         MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);
227         return NULL;
228     }
229  
230     p = &slabclass[id];
231     assert(p->sl_curr == 0 || ((item *)p->slots)->slabs_clsid == 0);
232  
233     /* fail unless we have space at the end of a recently allocated page,
234        we have something on our freelist, or we could allocate a new page */
235     if (! (p->sl_curr != 0 || do_slabs_newslab(id) != 0)) {
236         /* We don't have more memory available */
237         ret = NULL;
238     } else if (p->sl_curr != 0) {
239         /* return off our freelist */
240         it = (item *)p->slots;
241         p->slots = it->next;
242         if (it->next) it->next->prev = 0;
243         p->sl_curr--;
244         ret = (void *)it;
245     }
246  
247     if (ret) {
248         p->requested += size;
249         MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);
250     } else {
251         MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
252     }
253  
254     return ret;
255 }
slab优先从slots(空闲chunk空间列表)中申请空间,如果没有则尝试申请一个Page的新空间(do_slab_newslab()),申请新slab是会先判断是否进行slab_reasgin(重新分配slab空间,默认不开启)。

内存浪费
根据上述描述,Memcache使用Slab预分配的方式进行内存管理提升了性能(减少分配内存的消耗),但是带来了内存浪费,主要体现在:

Data Item Size <= Chunk Size,Chunk是存储数据项的最小单元,数据项的大小必须不大于其所在的Chunk大小。也就是说76B的数据对象存入96B的Chunk中,将带来96B-76B=20B的空间浪费。

Memcache是按照Page申请和使用内存的,当Page大小不是Chunk的整数倍时,余下的空间将被浪费。即如果PageSize=1M,ChunkSize=1000B,那么将有1024*1024%1000=576B的空间浪费。

Memcache默认是不开启slab reasign的,也就是说分配已经分配给一个slab的内存空间,即使该slab不用,默认也不会分配给其他slab的

案例分析:定长问题导致逐出
memcache的chunk分布是均匀的,这是为了通用性考虑,但是现实中一些场景chunk的分布是不均运的,例如为了减小对数据库的压力,对数据进行了全量缓存,为标识数据库中不存在的记录,向缓存中放置了一个stupidObject。这个对象大小是固定的,且该数据的量很大,导致该数据类型所在的slab占用了大量缓存空间。再一次调整对象结构时,修改了这个StupidObject大小,使其分布在另一个slab中,但是这个原分配的slab空间不会回收,空闲空间不足,导致大量逐出。

← Previous Archive Next →



运维网声明 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-16508-1-1.html 上篇帖子: Memcached源码分析之内存管理篇 下篇帖子: Memcached原理深度分析详解 结构图 模型
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

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

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

扫描微信二维码查看详情

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


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


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


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



合作伙伴: 青云cloud

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