memcached内存分配及回收初探
对memcached(后面简称mc) 的内存分配及回收机制进行了深度分析和测试,以下是一些学习的心得,和大家共同探讨一下,期望能抛砖引玉
mc简介:
mc是由LiveJournal技术团队开发的一套分布式对象缓存系统,基于c语言,目前应用十分广泛,它可以应对任意多个连接,使用非阻塞的网络I/O。它的使用非常简单和方便,最常用的功能不超过5个方法(set,get,delete...)。目前pconline的网站群基本上都是使用mc做缓存服务
mc在很多时候都是作为数据库前端缓冲使用的。因为它比数据库少了SQL解析、磁盘操作等开销,而且它是使用内存来管理数据的, 所以它可以提供比直接读取数据库更好的性能,在大型bbs系统中,访问同样的数据是很频繁的,mc可以大大降低数据库压力,使系统执行效率提升。 另外,mc也经常作为服务器之间数据共享的存储媒介,例如在SSO系统中保存系统单点登陆状态的数据就可以保存在mc中,被多个应用共享。
命题提出
前段时间登录系统出现了一个比较怪异的问题,刚刚登录的用户,还未到session过期时间就开始抛空异常,查看mc内存利用率不到60%,检查重启mc后问题得到缓解,后来小虎分析可能是mc中缓存的未过期数据被冲掉,于是有了下面的分析
mc内存分配机制简介
memcached默认情况下采用了名为Slab Allocator的机制分配、管理内存,Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,以完全解决内存碎片问题。
先来解释一下与Slab Allocator存储有关的几个术语:
Page:分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk。 Chunk:用于缓存记录的内存空间。 Slab Class:特定大小的chunk的组。
Growth Factor:增长因数,默认为1.25(较早的版本固定为2)
mc启动后,会根据这个factor,计算出从1M逐步递减的不同的slab,如factor=1.25时:
- ...</span>
第一列数据(slab class),为slab的编号;
第二列数据是chunk的大小,跟slab class是一一对应的关系,可以通俗的理解为slab就是存放一组相同大小chunk的集合,只不过这个集合是固定的(1M),
第三列数据,表示每种不同slab中的page可以存放的chunk个数,实际上等于1MB/ (chunk size),例如slab1中的chunk size是88B,那么这种slab中每个page中可以存放的chunk个数为 1MB / 88B ,约等于11915
很显然,slab的chunk size越大,其中的每个page包含的chunk数量就越少
如图所示:
新入对象时,会根据自身大小来匹配slab列表,比如100KB的对象,根据最小空间损失原则,会被放入到slab2(size:112B)对应的page下,如下图
这时,如果slab2下的page中有尚可以使用的chunk(即空闲的chunk或者过期的chunk),slab2会优先使用这些chunk,在没有chunk可用的情况下,mc会去内存中再申请一个page,然后切分成chunk,然后使用;需要注意的是,根据 Slab Allocator算法, 该实例中的100KB对象,是永远没有机会存放到其他slab(如slab3,slab4等等),即便是其他slab中有大量的可用chunk,细心的朋友会发现,这种机制很有可能会导致内存浪费严重,mc命中率降低等问题,对,这种问题真的存在,这也正是这种机制的缺点,下面会进行详细的分析和探讨
mc数据删除机制简介:
首先我们知道,缓存在mc中的数据,不会主动从内存中消失,也就是说mc不会监视记录是否过期,而是在client端执行get方法时才去检查时间戳是否过期(这样做的目的就是避免在过期监视上耗费cpu资源,以提高mc的响应能力);每次有新对象加入时,mc会优先将对象置于已超时的同一规格chunk中,然而,即使如此,内存仍然会发生追加新记录时空间不足的情况,那么,当mc内存耗完后,又是怎样处理新入的数据呢?mc有两种处理策略,一种是默认的LRU(Least Recently Used),指删除近段时间最少使用同规格chunk,再将对象塞入),另一种策略是存满即不可再存,除非有过期的对象,否则会报错
再回头看问题:
想必各位已经发现我们的登录状态数据是怎么被冲走的了,对,没错,LRU!在内存还有将近一半的情况下,就发生了LRU,为什么呢?这一半空闲的内存,表面是空闲的,实际上已经被mc将其打包成page分配到了其他stat里,而这些stat即便空闲、数据过期,也不会被mc回收已供其他繁忙的slab调用的。产生这种情况的直接原因就是,mc启动后,较大chunk size的slab同时间大量涌入mc,假设slab为20,这时,mc不得为slab 20分配大量的page,而在一小段时间后,slab 20中的chunk纷纷过期,但是它们曾经占用的page就永远不会被mc主动回收了,除非再有与slab20同规格的对象进入时,这些page才会重新得到使用的机会,与此形成强烈对比的slab2(chunk size = 112B)却处于无可用chunk,无内存已供分配新page的境地,这个时候,LRU出场了,会按时间相关度清理掉一些尚未过期的slab2 chunk,如此造成缓存来去匆匆,实际上性能严重下降
LRU:
首先,在memcache分配的时候,初始化会去分配一系列的slab,例如初始的slab为88k,然后factor为1.25,那么你会发现开始的时候 就会有:88,112,44,....一直到1M大小的slab各一个,假如对象集中在其中某一个区间,那么很快那个slab就会分配满,此时如果内存还有,那么就会新建一个同样大小的slab作为链挂在第一个同等大小的slab上,如果说内存也满了,slab也满了,那么就开始LRU算法了。 但是Memcached的LRU算法是针对slab的,而非全局的,如果数据集中在一个slab上,那么初始化的时候其他几个slab肯定就浪费了,同时,如果slab的大小和对象的大小有比较大的差异,那么浪费的将会更加巨大。所以在评估使用 memcache初始大小和factor的时候需要注意这些,选择适合的初始化size和factor,减少slab分配的浪费。
解决思路
思路1.通用解决方法: 调整growth factor
逐步调整growth factor,并观察chunks的分布,尽量将数据对象的大小控制到一定区间内,启动时加入-f参数即可,在factor=1.25时有39组slab
- .25s
- ...
当growth factor调大成2以后,slab class明显变少,只有13组了
很显然,factor越小,chunk匹配得就越精准,但是slab组就会分得越多,而产生LRU的机会也会增加,factor越大,分组就越少,产生LRU的机会就越小,但是chunk匹配精准度会有所下降,如在数据大小为130B时,如果f=1.25,mc会将其放入class3(chunk size = 144B),浪费的空间为14B;如果f=2.0,mc会将其放入class2(class size = 256)中,浪费的空间为126B,相当惊人,所以factor的大小设置在一个比较平衡的值,一般以默认的1.25较为理想。
思路2.动态调整slab中page的数量
大体思路是使用java客户端监控程序,定时检查每个slab的使用情况,动态调整每个slab中的page,将某个比较空闲的slab中的page移动到另外的slab中去,不过mc的开发人员认为在mc中遍历slab和page移动会造成较大系统开销,所有没有提供直接的api已供调用,一直屏蔽调用,在1.28版本以前,还可以通过在memcached.h中添加宏#define ALL_SLABS_REASIGN并重新编译使slabs reassign命令生效,但在使用过程中,出现了大量的性能问题,mc稳定性下降,而且在数据移动过程中,会导致mc不可写的问题,针对这些弊端,mc的开发团队在其后续版本中已经彻底删除相关处理逻辑,我也尝试对1.28的源码进行修改和编译,基本可用,但是何时调用 reassign,以及reassign后对系统造成的影响仍然需要进一步的数据分析,继续跟进中。。。
memcached内存分配及回收初探的更多相关文章
- Java深入 - Java 内存分配和回收机制
Java的GC机制是自动进行的,和c语言有些区别需要程序员自己保证内存的使用和回收. Java的内存分配和回收也主要在Java的堆上进行的,Java的堆中存储了大量的对象实例,所以Java的堆也叫GC ...
- Java 对象内存分配与回收
JVM内存区域模型: * 程序计数器,内存区域极小,是当前线程的字节码执行行号指示器: * 虚拟机栈.本地方法栈,即平时所说的“栈”,是虚拟机用来执行方法(包括Java.非Java方法)时,使用的临时 ...
- 最简单例子图解JVM内存分配和回收
一.简介 JVM采用分代垃圾回收.在JVM的内存空间中把堆空间分为年老代和年轻代.将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象.年轻代中又被分 ...
- java虚拟机的内存分配与回收机制
分为4个方面来介绍内存分配与回收,分别是内存是如何分配的.哪些内存需要回收.在什么情况下执行回收.如何监控和优化GC机制. java GC(Garbage Collction)垃圾回收机制,是java ...
- Android的内存分配与回收
想写一篇关于android的内存分配和回收文章的想法来源于追查一个魅族手机图片滑动卡顿问题,我们想了很多办法还是没有避免他不停的GC,所以就打算详细的看看内存分配和GC的原理,为什么会不断的GC,GC ...
- Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法
在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...
- 最简单例子图解JVM内存分配和回收(转)
本文转自http://ifeve.com/a-simple-example-demo-jvm-allocation-and-gc/ http://www.idouba.net/a-simple-exa ...
- Java基础-Java中的内存分配与回收机制
Java基础-Java中的内存分配与回收机制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一. 二.
- JVM内存分配与回收
1.内存分配与回收策略 内存自动管理:自动化的解决了对象内存分配和回收对象内存的问题. 一般在堆上分配对象,也可能经过JTI编译后间接在栈上分配. 主要分配在新生代的Eden区,如果启动了本地线程分配 ...
随机推荐
- 查询记录rs.previous()使用
查询记录rs.previous()使用 假如查询一个数据表,假设没有记录就显示提示信息.有就所有显示出来查询结果.这时假设是有查询结果的话就须要进行两次查询.第一次查完记录指针指向最后一条记录.開始第 ...
- blog_导航
blog导航 在左下侧:随笔分类 简要做个导航图放到这儿方便大家查阅 直奔爬虫: 点击-> 直奔oracle:点击->
- JS中call、apply的用法说明
JS Call()与Apply()的区别 ECMAScript规范给所有函数都定义了Call()与apply()两个方法,call与apply的第一个参数都是需要调用的函数对象,在函数体内这个参数就是 ...
- Oracle\MS SQL Server Update多表关联更新
原文:Oracle\MS SQL Server Update多表关联更新 一条Update更新语句是不能更新多张表的,除非使用触发器隐含更新.而表的更新操作中,在很多情况下需要在表达式中引用要更新的表 ...
- Net社区虚拟大会
微软“.Net社区虚拟大会”dotnetConf2015:关键词:.NET 创新.开源.跨平台 去年 11 月的时候,微软开源了 .NET CoreFX,然后是今年 2 月份的 .NET CoreCL ...
- IOS中 类扩展 xib
一.类扩展(class extension,匿名分类) .格式 @interface 类名 () { // 成员变量... } // 方法声明... @end .作用 > 写在.m文件中 > ...
- 网页动态切换母版页(MasterPage)
原文:网页动态切换母版页(MasterPage) 是否可以变更网页的母版页(MasterPage)呢?某.aspx在创建时,已经附加入某一母版页(MasterPage)了,现需要.aspx动态变更母版 ...
- SVN:One or more files are in a conflicted state
解决代码冲突 如果commit时出现"You have to update your work copy first."红色警告,说明版本库中的此文件已经被其他人修改了. 请先点& ...
- android意图传递参数返回结果(六)
例如,有两页a,b. a参数传递到页面b页面,b后,将获得的参数的处理页,然后将结果传回与参数的页面a. 1.a主页MainActivity的代码类型,如以下: private Button butt ...
- LINUX下FD_SET介绍
刚刚了解了linux下select系统调用,函数原型是 #include <sys/select.h> #include <sys/time.h> int select(int ...