结构介绍
MemCache有几种内存概念:slab_class
、slab
、chunk
slab_class
:拥有同样chunk大小的slab被组织在一起,成为slab_class。
slab
:MemCache将内存空间分为一组slab,slab下面是若干个chunk,默认大小为1M。
chunk
:chunk是真正存储数据的地方,同一个slab里面chunk的大小是一样的。chunk的增长因子由-f指定,默认1.25,起始大小为48字节;
MemCache中的value过来存放的地方是由value的大小决定的,value总是会被存放到与chunk大小最接近的一个slab中,比如slabclass[1]的chunk大小为80字节、slabclass[2]的chunk大小为100字节、slabclass[3]的chunk大小为128字节(相邻slab_class内的chunk基本以1.25为比例进行增长,MemCache启动时可以用-f指定这个比例),那么过来一个88字节的value,这个value将被放到2号slab中。放slab的时候,首先slab要申请内存,申请内存是以slab为单位的,所以在放入第一个数据的时候,无论大小为多少,都会有1M大小的内存被分配给该slab。申请到后,slab会将内存按chunk的大小进行切分,这样就变成了一个chunk数组,最后从这个chunk数组中选择一个用于存储数据。
slab_class
这里是slab_class结构的定义:
|
|
item
在MemCache中,item是用于存储数据的结构
|
|
高效的内存管理策略
循着 slabs_init()
方法看下去发现,slab_class的个数是有限制的,数值定义在 memcached.h
文件当中。
|
|
数值是直接写死的,也就是说我们没有办法改变它。当然我们也没有必要去修改他,因为chunk最大值是有限制的,最大不可以超过slab的大小(不然如果chunk超过了slab的大小,slab还怎么放得下这个chunk呢?)。同时,memcache在为item选择合适的slab_class时,会根据item的大小从前往后遍历所有slab_class寻找合适的slab_class,若slab_class的数目过多也会造成性能的浪费。同时默认的设置中,item的最大值也是限制为了1MB
|
|
slab的申请以及回收过程
|
|
slots 是回收的 item 链表, 从某个 slabclass 分配出去一个 item, 当 item 回收的时候,不是把这 item 使用的内存交还给 slab, 而是让这个 item 挂在 slots 链表的尾部。
sl_curr 表示当前链表中有多少个回收而来的空闲 item.
chunk中的内存使用完毕后不是让系统回收内存,而是放置于slot当中,如果有需求时可以直接取掉头结点作为空间使用。若内存不足,则申请一块新的slab。这个回收策略大大减小了内存释放回收过程中造成的性能损耗。do_slabs_alloc()
这个函数做的事情是,检查slots当中是否有空闲的内存可以使用,有则从中取出使用,没有才从slab中分配一个空间的chunk
理解了memcache的内存申请和回收原理后,同理的,我们可以看一看内存的回收过程, do_slabs_free()
函数。
|
|
do_slabs_free()
函数做的事情是把需要回收的item插入到slots链表中,这样子这个item就已经被回收完毕了。下次需要新的内存时直接从slots中取出即可,对内存的操作实际上只是对指针的操作而已,这样的效率是非常高的。
item
为一个item分配存储空间的时候,具体的操作是这样的:
1、首先,计算该item占用的空间大小,只有知道了它的大小,才能知道它需要存储在哪个桶中。一个item的大小包括它的item结构体大小部分、名字长度部分、状态标识部分、内容大小部分等的总和,实现方法为 item_make_header
函数。
2、然后寻找合适的slab用于存储,这一部分主要是比较item 和各slab桶的大小,寻找最合适的slab,此部分代码是文件 slabs.c
中的 slabs_clsid()
函数。该函数做的事情上文已有所提及,会根据item的大小从前往后遍历所有slab_class,寻找大小合适的slab_class并返回。
3、从对应slab的tail队列中寻找是否存在过期的item,如果有,清除掉,此处操作最多尝试5次。
4、如果第3步操作失败,并且在对应slab中分配空间失败,那么从slab对应的tail队列中删除没有被引用的item,且最多也是尝试5次。
5、尝试从slab中分配空间。
6、如果第5步失败,会从slab对应的tail队列中删除3个小时(默认)之前的正在引用的item。
7、然后尝试从slab中分配空间。如果失败,返回NULL,成功则会设置item对应的一些信息,返回成功标识。
item的删除过程:
1、设置已被删除状态。并从hash表中删除,次部分代码调用的是 assoc_delete()
2、从LRU链中删除。函数 item_unlink_q()
。
3、如果要清除item占用的资源,则调用函数 do_item_remove()
和 item_free()
,释放占用内存空间。
另外还提供了一些其他操作,分别包括,获取某个item(会判断是否过期),获取某个item(不判断是否过期),客户端通过flush_all操作清空所有过期item,item的新值替换,访问时间更新等。
当然,有item的删除操作,就要有相应的加入hash表和LRU链的操作。
另外,还提供了一些item和slab状态函数。
cas属性
cas即compare and set或者compare and swap,是实现乐观锁的一种技术,乐观锁是相对悲观锁来说的,所谓悲观锁是在数据处理过程中,完全锁定,这种能完全保证数据的一致性,但在多线程情况下,并发性能差,通常是使用各种锁技术实现;而乐观锁是通过版本号机制来实现数据一致性,过程中会使用CPU提供的原子操作指令,乐观锁能提高系统的并发性能,Memcached使用cas是保证数据的一致性,不是严格为了实现锁。
Memcached是多客户端应用,在多个客户端修改同一个数据时,会出现相互覆盖的情况,在这种情况下,使用cas版本号验证,可以有效的保证数据的一致性,Memcached默认是打开cas属性的,每次存储数据时,都会生成其cas值并和item一起存储,后续的get操作会返回系统生成的cas值,在执行set等操作时,需要将cas值传入
写在最后
代码版本:
MemCache 1.4.15
这个是我阅读的源码版本,旧版的代码可读性比较强,同时也有童鞋对此版本代码做了比较详细的注释,在此给大家分享一下。
https://github.com/weiweikaikai/Mecached-Source-code-analysis