memcached机制
•守护进程机制
–UNIX daemon
•Socket事件处理机制
–non-blocked:非阻塞
–libevent:异步事件处理
–epoll/kqueue
•内存管理机制
–slab:内存分配机制
–LRU:对象清除机制
–Hash机制:快速检索item
•多线程处理机制:pthread(POSIX)线程模式
–编译时开启:./configure –enable-threads
–目前还比较粗糙,锁机制locking不够完善
–负载过重时,可以开启(-t线程数为CPU核数)
•SLAB内存处理机制
–提前分配大内存slab 1MB(因为page默认为1MB),再进行小对象填充chunk
–避免大量重复的初始化和清理à减轻内存管理器负担
–避免频繁malloc/freeà系统碎片
•懒惰检测机制
–不检测item对象是否超时
–get时检查item对象是否应该删除
•懒惰删除机制
–删除item对象时,不释放内存,作删除标记,指针放入slot回收插槽,下次分配的时候直接使用
Page为内存分配的最小单位
Memcached的内存分配以page为单位,默认情况下一个page是1M,可以通过-I参数在启动时指定。如果需要申请内存 时,memcached会划分出一个新的page并分配给需要的slab区域。page一旦被分配在重启前不会被回收或者重新分配(page ressign已经从1.2.8版移除了)
Slabs(slab class)划分数据空间
Memcached并不是将所有大小的数据都放在一起的,而是预先将数据空间划分为一系列slabs(slab class),每个slab只负责一定范围内(chunk数据大小决定)的数据存储。每个slab只存储大于其上一个slab的size并小于或者等于自己最大size的数据。例如:slab 3只存储大小介于137 到 224 bytes的数据。如果一个数据大小为230byte将被分配到slab 4中。memcached默认情况下下一个slab存储数据的最大值为前一个的1.25倍,这个可以通过修 改-f参数来修改增长比例。
Chunk才是存放缓存数据的单位
Chunk是一系列固定的内存空间,这个大小就是管理它的slab的最大存放大小。例如:slab 1的所有chunk都是104byte,而slab 4的所有chunk都是280byte。chunk是memcached实际存放缓存数据的地方,因为chunk的大小固定为slab能够存放的最大值, 所以所有分配给当前slab的数据都可以被chunk存下。如果时间的数据大小小于chunk的大小,空余的空间将会被闲置,这个是为了防止内存碎片而设 计的。例如下图,chunk size是224byte,而存储的数据只有200byte,剩下的24byte将被闲置。
slab内存分配
Memcached在启动时通过-m指定最大使用内存,但是这个不会一启动就占用,是随着需要逐步分配给各slab的。
如果一个新的缓存数据要被存放,memcached首先选择一个合适的slab,然后查看该slab是否还有空闲的chunk,如果有则直接存放进去;如 果没有则要进行申请。slab申请内存时以page为单位,所以在放入第一个数据,无论大小为多少,都会有1M大小的page被分配给该slab。申请到 page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk的数组,在从这个chunk数组中选择一个用于存储 数据。如下图,slab 1和slab 2都分配了一个page,并按各自的大小切分成chunk数组。
Memcached内存分配策略
memcached的内存分配策略就是:
按slab需求分配page,各slab按需使用chunk存储。
这里有几个特点要注意,
- Memcached分配出去的page不会被回收或者重新分配
- Memcached申请的内存不会被释放
- slab空闲的chunk不会借给任何其他slab使用
- 新版本中Page可以调配给其它的Slab,shell> memcached -o slab_reassign,slab_automove
Memcached的LRU机制
为了规避内存碎片问题,Memcached采用了名为SlabAllocator的内存分配机制。内存以Page为单位来分配,每个Page分给一个特定长度的Slab来使用,每个Slab包含若干个特定长度的Chunk。实际保存数据时,会根据数据的大小选择一个最贴切的Slab,并把数据保存在对应的Chunk中。如果某个Slab没有剩余的Chunk了,系统便会给这个Slab分配一个新的Page以供使用,
如果没有Page可用,系统就会触发LRU机制,通过删除冷数据来为新数据腾出空间,这里有一点需要注意的是:LRU不是全局的,而是针对Slab而言的。
slab内存结构图:二维数组链表
slab内存分配实例
Item内存分配(chunk中的实际数据)
新建Item分配内存过程
•快速定位slab classid
–计算key+value+suffix+32结构体,如90byte
–如果>1MB,无法存储丢弃
–取最小冗余的slab class
•如:有48,96,120,存90会选择96
•按顺序寻找可用chunk
–slot:检查slab回收空间slot里是否有剩余chunk
•delete:delete时标记到slot
•exptime:get时检查的过期对象标记到slot
–end_page_ptr:检查page中是否有剩余chunk
–memory:内存还有剩余则开辟新的slab
–LRU:Slab内部扫描Item双向链表50次
减少内存浪费
•调优方法:
–f参数:factor增长因子
–n参数:chunk初始值
•slab尾部剩余空间
•如classid=40中,两个chunk占用了1009384byte,就有1048576-1009384=39192byte被浪费
•解决办法:规划slab=chunk*n整数倍
• slab中chunk利用率低:申请的slab只存放了一个Item
•解决办法:规划slab=chunk
•chunk存储Item浪费
•如I tem是100,存到128字节chunk,就有28字节浪费
•解决办法:规划chunk=Item
使用合适的factor,减少浪费
•-f参数:默认为1.25,曾经为2
•值越小,slab中chunk size差距越小,内存浪费越小
•1.25适合缓存几百字节的对象
根据数据分布调整factor
•非均匀分布,即数据长度集中在几个区域内
–如保存用户Session
–
•更极端的状态是等长数据
–如定长键值,定长数据
–多见于访问、在线统计或执行锁
–
•计算Item长度
–key键长+suffix+value值长+结构大小(32字节)
因为优秀,所以不足
•Can’t dump
–无法备份,重启无法恢复
•Can’t iterate over keys
–无法查询
•Not persistent
–没有持久化,重启全部丢失
•Not redundant
–单点故障failover
•No Sessions
–崩溃没法查找原因
•No security
–任何机器都可以telnet,需要放在防火墙后
•内存问题
–LRU是slab局部,没有全局
–有空间浪费
•日志问题
–没有合理的日志
•集群问题
–集群增加机器成本高