从某个意义上讲,资源总是有限的,衡量一个计算机处理能力的指标有很多, 根据不同的应用需要也会有不同的指标,比如3D游戏对显卡的性能有要求,Web服务器对吞吐量及响应时间有要求,而缓存服务器对存取效率着重要求, 通常CPU、内存及硬盘的读取和计算速度具有决定性的作用,在同一时刻这些资源是有限的, 正是因为有限我们才需要合理的利用他们。
我们此次就 memcached的
存储模型
、hashtable
、slabs
三个方面进行解析,分析其存储机制的精妙设计
首先我们引入几个概念,item
、chunk
、slabclass
,记得很久之前看了一段很形象的描述(@特别鸣谢):
整个 memcached存储就像 我们常用的
田字格写字本
- 写字本有很多页,每页纸张大小一致,相当于
slabclass
- 每页有很多个大小一致的格子,相当于
chunk
- 每个格子都是用来写字的,写一个字或者多个字,字 就相当于
item
slabclass
memcached 的内存管理单位,无论 扩容还是回收 都是以 slabclass 为单位的,slab机制 实际就相当于 内存池机制。memcached 启动的时候会初始化一个 slabclass数组,每个 slabclass 默认大小 1M。chunk
chunk内存块(也称为 slab
) 跟 slabclass 一样都是实实在在的内存空间。chunk 存在的意义在于划分不同规格的内存块,以存储不同大小的数据,以免存储资源的浪费。每个 slabclass 都会进行 chunk 划分,不过不同的 slabclass 划分的 chunk 个数不同;slabclass memory(1MB) = chunk nums * perchunk memory
,比如有的 slabclass 有 chunk 2个,每个 chunk 占 0.5MB;而有的 slabclass 有 4个 chunk,那么对应每个 chunk 占 0.25MB。每个 slabclass 划分的 单位chunk大小 是线性递增的,也可以理解为 每个 slabclass 划分的 chunk 个数是线性递减的,取决于 -f 控制的 settings.factor,默认 1.251234567891011121314151617181920212223242526272829303132333435363738394041[wettper@web-lovers memcached-src]$ ./memcached -u wettper -m 512 127.0.0.1 -p 11211 -vvslab class 1: chunk size 96 perslab 10922slab class 2: chunk size 120 perslab 8738slab class 3: chunk size 152 perslab 6898slab class 4: chunk size 192 perslab 5461slab class 5: chunk size 240 perslab 4369slab class 6: chunk size 304 perslab 3449slab class 7: chunk size 384 perslab 2730slab class 8: chunk size 480 perslab 2184slab class 9: chunk size 600 perslab 1747slab class 10: chunk size 752 perslab 1394slab class 11: chunk size 944 perslab 1110slab class 12: chunk size 1184 perslab 885slab class 13: chunk size 1480 perslab 708slab class 14: chunk size 1856 perslab 564slab class 15: chunk size 2320 perslab 451slab class 16: chunk size 2904 perslab 361slab class 17: chunk size 3632 perslab 288slab class 18: chunk size 4544 perslab 230slab class 19: chunk size 5680 perslab 184slab class 20: chunk size 7104 perslab 147slab class 21: chunk size 8880 perslab 118slab class 22: chunk size 11104 perslab 94slab class 23: chunk size 13880 perslab 75slab class 24: chunk size 17352 perslab 60slab class 25: chunk size 21696 perslab 48slab class 26: chunk size 27120 perslab 38slab class 27: chunk size 33904 perslab 30slab class 28: chunk size 42384 perslab 24slab class 29: chunk size 52984 perslab 19slab class 30: chunk size 66232 perslab 15slab class 31: chunk size 82792 perslab 12slab class 32: chunk size 103496 perslab 10slab class 33: chunk size 129376 perslab 8slab class 34: chunk size 161720 perslab 6slab class 35: chunk size 202152 perslab 5slab class 36: chunk size 252696 perslab 4slab class 37: chunk size 315872 perslab 3slab class 38: chunk size 394840 perslab 2slab class 39: chunk size 524288 perslab 2......item
memcached 的最小存储单元,主要存储 数据、key值 等等其他关系信息(后续会详细介绍)。每个 item 结构体被封装以后根据其 大小放入 对应的 chunk 中,选择规格比 item 大但是最接近的 chunk 中,虽然会 产生部分内存碎片,不过经过 chunk 机制的控制基本将 碎片控制在了可 接受范围内;而 item 存储在 同样规格的哪个 chunk 里面 则是通过hashtable
去控制,每个 chunk 里面会存储一个 item 单向链表,至于为什么使用 hashtable,后续会详细介绍
存储模型
slabs 模型
- 每个 slabclass 除了分割成若干个 chunk 内存块之外,还会对应一个 free list 的双向链表,slabclass 结构体的 **slot 对应这个空闲列表的头部指针;
- 内存分配的时候如果空闲列表中有 item,则从空闲列表中取,否则会分配一个 chunk,然后切割成 N个 item 放进 free list 中;
- item 封装完毕以后,根据大小选择 slabclass,随后检测 slabclass 中是否有 free list,如果有则从 free list 分出来一个 item 进行占位
hashtable 模型
什么是hashtable
Hash
,一般翻译为 “散列”,也可以称为 “哈希”,就是把任意长度的 input 通过散列算法,变换成固定长度的 output,该输出就是散列值。这种转换是一种压缩映射,简单的说,就是 一种将任意长度的消息压缩为某一固定长度的消息摘要函数。
那么 hashtable
呢? 众所周知,数组的特点是 寻址容易,但是插入删除都比较困难;而 链表是 插入删除十分简单,但是寻址困难。为了综合两方的优势,做出 寻址容易 而 插入删除也比较遍历的 数据结构呢,so,就有了 hashtable 哈希表
,其实就是一种 “链表的数组”,具体逻辑如下:
- 把 key 通过一个固定的 hash 算法函数(例如 hashtable(key, value))转化成一个 固定的整型数值,然后就该 数值对 链表数组长度 求余,求余结果作为 这个key 所在数组的下标;(
元素特征转换成数组下标的方法 即是 散列法,但是散列法肯定不止上述一种,常用的还有 平均散列法、斐波那契(Fibonacci)散列法 等
) - 将 value push 至这个数组下标内存空间里的链表尾部
- 当对 hashtable 进行查询的时候,再次对 key 进行哈希操作,求出数组下标,然后对数组下标对应的链表进行查询
关于 memcached item 的 hashtable
- hashtable 的 table 长度是
hashsize(n)
计算出来的,n值可以自己设置(12 < n < 64),长度默认为 65536,通常够用了 - input 的 key 值会被
hash(key, nkey)
为一个 uint32_t 的值 hv - 通过 hv 与 table长度进行 按位与计算
hv & hashmask(hashpower)
,计算出数组下标 - 然后将 item 挂到这个数组下标下,对应 item->h_next
源码解析
由于有些方法里的代码量比较大,我们这里按照 典型的代码片段进行解析,同志们可以根据文章提示的代码位置 和 代码里面的关键词 在源码中搜素,可能数据结构一些元素 看不太懂什么意思,没关系,先混个脸熟,后面看完回头再看过来就明白了
常用数据结构
item
为 memcached 中最小的数据存储单位
slabclass_t
slabclass 数据结构
slabs 源码
slabs_init
slabclass 初始化
slabs_clsid
slabs检索,查询 item 应该存入哪个slabsclass
do_slabs_alloc
向 slabclass 申请一个item,在调用函数之前,已经调用 slabs_clsid 函数确定本次申请是向哪个 slabclass_t 申请 item 了,参数 id 就是指明是向哪个 slabclass_t 申请item。如果该 slabclass_t 是有空闲 item,那么就从 freelist 队列分配一个,如果没有空闲item,那么就申请一个内存页并拆分至 freelist,再从新申请的页中分配一个 item 返回值为得到的item,如果没有内存了,返回NULL
do_slabs_newslab
分配新的 slab
split_slab_page_into_freelist
切分slab至 freelist
do_slabs_free
slabs_free
释放 item,这里的释放是将 item 回收至 freelist 空闲列表
|
|
hashtable 源码
assoc_init
hashtable 初始化
item_get
我们从 item_*
方法中选取 item_get 进行解析 hashtable 的数组定位
do_item_get
方法里有个 assoc_find
操作,用来查找 hashtable 数据
assoc_insert
新增一个 item,一般会先找到 key 对应的 hashtable,然后 头插法 放入 hashtable 的头部
assoc_delete
删除一个 item则不一样,一般会先找到 key 对应的 hashtable,然后遍历 链表 找到对应的 item 并删除,这个查找 item 则使用的是 _hashitem_before
方法
|
|
本文作者: wettper
本文链接: http://www.web-lovers.com/memcached-source-storage.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!