Memcached哈希性能优化(八)——总结报告
转自:http://m.blog.csdn.net/blog/hzwfz1989/39120005
Memcached哈希性能优化报告
一、 Memcached分析
1. 内存管理
- 和一般的内存管理差不过, memcached从操作系统获取到一大块内存后, 便把这大块内存划分为各种大小的chunk块, chunk块大小按照比例逐渐递增,这个可以由用户来指定完成。
- 每个slabclass含有多个slab,一个slab是一个大小为1M的内存块,这个slab块的会被按照chunk块的大小进行分割进行分配。
- 从 slab管理器分配item的时候, 首先计算item的大小, 找到大小刚好大于等于某个chunk块size所在的slabclass,然后进行分配。
- 分配的逻辑很简单,如果有空闲的或是回收回来item的话,则直接分配从这个chunk块,否则尝试申请新的slab的然后进行分配,再要没有就只能返回NULL让LRU去替换了。
- 存在内存碎片, 比如说, 将48字节的item存储到一个64字节的 chunk, 就有16字节的内存浪费。
- 另外,由于slab是唯一的,必须采用slab_lock保证分配的原子性,也就是说如果多线程分配的话,如果cache_lock间冲突假设没有的话,到这个slab去申请内存的话也只能是单一的申请,所以决定了插入的过程的最终瓶颈就是这里了,而这里对内存的管理如果把他也分块的话导致后续的逻辑比较难处理,所以这个其实没有处理。
2. hash和LRU
- 链式hash的实现很经典,但是由于是链表保存,数据的局部性很不好,所以内存的访问效率就会有所下降。
- LRU的那个双向链表的实现,插入和读取都要调整item在LRU表中的位置,这2个操作明显需要上锁,其实读取本身不需要上锁的,但是要调整LRU的位置,所以没有办法在do_item_link和do_item_unlink中我们都能发现要上锁cache表。
- 如果采用开放寻址法使用的话,那么hash的查找效率可能就不高了,所以hash的效率还是极为关键的。
3. 线程模型
- 初始化主线程event_base,和worker线程
- 建立worker线程的通知管道
- 注册worker线程管道的libevent事件
- 初始化每个worker的CQ队列
- 启动worker线程,主线程监听socket事件,worker线程监听socket的读写事件
二、Memcached的优化内容
1. 分块hash优化
- 首先把hash表分块,每个分块由不同的线程进行处理,当然可以绑定到固定的核上去,这个有个memc3就是这个绑定的
- 每个hash块拥有有自己的LRU表和hash的表,分块由自己的LRU和hash表进行处理操作
2. clock算法优化
目的其实很单纯,牺牲命中率和淘汰次数,提高get的读写性能,尤其是多核的情况下这个Clock置换算法大致描述如下
2类(A=0, M=1):表示该页最近未被访问,但已被修改,并不是很好的淘汰页。
3类(A=1, M=0):最近已被访问,但未被修改,该页有可能再被访问。
4类(A=1, M=1): 最近已被访问且被修改,该页可能再被访问。其执行过程可分成以下三步:
(1) 从指针所指示的当前位置开始,扫描循环队列,寻找A=0且M=0的第一类页面, 将所遇到的第一个页面作为所选中的淘汰页。在第一次扫描期间不改变访问位A。
(2) 如果第一步失败,即查找一周后未遇到第一类页面,则开始第二轮扫描,寻找A=0且M=1的第二类页面,将所遇到的第一个这类页面作为淘汰页。在第二轮扫描期间,将所有扫描过的页面的访问位都置0。
(3) 如果第二步也失败,亦即未找到第二类页面,则将指针返回到开始的位置,并将所有的访问位复0。然后重复第一步,如果仍失败,必要时再重复第二步,此时就一定能找到被淘汰的页。
- 只采用了时间进行标记,在update和get的时候记录下最新时间,在替换的时候检验这个时间是不是超期,如果超期则进行替换,如果没有超期就不替换。
- 为了能够支持循环查找,讲LRU的链表改成了双向的循环链表,同时添加hand指针,指向需要替换的元素,如果超期就替换,如果不超期则进行下一次尝试,如果失败的话,则就将这个元素替换出去
- 更新的过程中,不将元素放置到head处,只更新访问时间,这样的话就不用断掉链表,这样不用进行item _unlink和item_link的过程
3. hash算法优化
- 首先检测映射到的bucket,看它是不是被占用,如果未被占用那就直接使用
- 如果已被占用,那么采用线性探测法探测到位置pos
- 如果pos的位置离bucket的位置大于给定的阈值H,那就调整这个上面的位置,使得这个H-1的bucket上出现空槽,如果没有空槽,resize哈希表然后再次进行尝试。
4. tag查询优化
- 首先,另外采用一个hash函数计算算出一个1字节大小tag,直接保存在 hashtable 的对应item里面。
- 然后查找的时候,先比较tag是不是一致,如果一致,再去比较 key。
三、优化结果
[OVERALL], RunTime(ms), 45906.0 [OVERALL], Throughput(ops/sec), 108917.52712063782 [INSERT], Operations, 4999968 [INSERT], AverageLatency(us), 428.84410700228483 [INSERT], MinLatency(us), 123 [INSERT], MaxLatency(us), 59953 [INSERT], 95thPercentileLatency(ms), 0 [INSERT], 99thPercentileLatency(ms), 1 [INSERT], Return=0, 4999968
[OVERALL], RunTime(ms), 53169.0 [OVERALL], Throughput(ops/sec), 94039.15815606838[INSERT], Operations, 4999968 [INSERT], AverageLatency(us), 497.43873040787463 [INSERT], MinLatency(us), 120 [INSERT], MaxLatency(us), 156936 [INSERT], 95thPercentileLatency(ms), 0 [INSERT], 99thPercentileLatency(ms), 6 [INSERT], Return=0, 4999968比较接近上限
[OVERALL], RunTime(ms), 77420.0 [OVERALL], Throughput(ops/sec), 64582.381813484884 [INSERT], Operations, 4999968 [INSERT], AverageLatency(us), 722.7500804005145 [INSERT], MinLatency(us), 120 [INSERT], MaxLatency(us), 118476 [INSERT], 95thPercentileLatency(ms), 1 [INSERT], 99thPercentileLatency(ms), 12
[OVERALL], RunTime(ms), 167550.0 [OVERALL], Throughput(ops/sec), 119367.16204118174 [READ], Operations, 19959611 [READ], AverageLatency(us), 389.58720097300494 [READ], MinLatency(us), 94 [READ], MaxLatency(us), 58364 [READ], 95thPercentileLatency(ms), 0 [READ], 99thPercentileLatency(ms), 3
[OVERALL], RunTime(ms), 168112.0 [OVERALL], Throughput(ops/sec), 118968.11649376607 [READ], Operations, 19960241 [READ], AverageLatency(us), 390.19616010648366 [READ], MinLatency(us), 97 [READ], MaxLatency(us), 166104 [READ], 95thPercentileLatency(ms), 0 [READ], 99thPercentileLatency(ms), 3
四、总结
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。