[Memcached] Slab钙化问题
引子
Memcached采用LRU(Least Recent Used)淘汰算法,在内存容量满时踢出过期失效和LRU数据,为新数据腾出内存空间。不过该淘汰算法在内存空间不足以分配新的Slab情况下,这时只会在同一类Slab内部踢出数据。即当某个Slab容量满,且不能在内存足够分配新的Slab,只会在相同Slab内部踢出数据,而不会挪用或者踢出其他Slab的数据。这种局部剔除数据的淘汰算法带来一个问题:Slab钙化。
实践
1 搭建一个64M、growth factor=1.25的MC节点。
2 用item数据写满MC 192B的chunk,因为已经有evicted数量,所以192B chunk肯定已经写满,如图1所示。
图1 memcached-tool结果3 计算内存利用率:bytes(59066176)/limit_maxbytes(67108864)=88%,已经达到growth factor=1.25的期望内存利用率,MC期望内存利用率计算方法请参考拙作《期望内存利用率计算方法》,所以内存已满,如图2所示。
图2 stats结果4 flush_all删除所有数据,从图3看item仍占用192B的chunk size,MC删除机制是数据不会真正从内存中消失,只要被其他数据覆盖,MC不会主动删除Slab chunk已存在的数据。
图3 memcached-tool结果5 再用5万个96B的item写入MC,一共写50000*96B/1024/1024=4M数据,远远小于64M,但是只能写入96B*10922=1MB,即只能写一个Page,还是有很多96B的item被Evicted,如图4所示。
即使192B的chunk数据已经被清除,MC淘汰策略是淘汰相同的Slab class数据,96B的item也不会重新使用192B的chunk size,只会使用原有启动Memcached时分配的1MB Slab class(Chunk size 96B),这就是所谓的Slab钙化问题。
图4 memcached-tool结果Slab钙化可以解释这个问题:为什么我写入比较新的数据,但被淘汰了?
假设Slab有各种规格(64~ 1M字节),比如应用存入的大部分数据大小在 64 ~ 128 字节范围内,那么这些数据会存储在128个字节大小的Slab chunk中,这些Slab chunk以链表的方式连接在一起。当已经没有空余的内存分配新的Slab,如果这时候写入10K新数据,且之前并没有这么大的数据写入时,那么这条新数据可以写入成功。但是当下次再写入10K数据时,第一次写入的10K数据就会被逐出。当下一次写入的新数据在64 ~ 128字节时,128字节大小的Slab链表上的数据会以LRU方式淘汰,所以LRU只会淘汰同一级别的Slab数据。
Slab钙化降低内存使用率,如果发生Slab钙化,有三种解决方案:
1) 重启Memcached实例,简单粗暴,启动后重新分配Slab class,但是如果是单点可能造成大量请求访问数据库,出现雪崩现象,冲跨数据库。
2) 随机过期:过期淘汰策略也支持淘汰其他slab class的数据,twitter工程师采用随机选择一个Slab,释放该Slab的所有缓存数据,然后重新建立一个合适的Slab。
3) 通过slab_reassign、slab_authmove参数控制。