对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时:

  1. ...</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

  1. .25s
  2. ...

当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内存分配及回收初探的更多相关文章

  1. Java深入 - Java 内存分配和回收机制

    Java的GC机制是自动进行的,和c语言有些区别需要程序员自己保证内存的使用和回收. Java的内存分配和回收也主要在Java的堆上进行的,Java的堆中存储了大量的对象实例,所以Java的堆也叫GC ...

  2. Java 对象内存分配与回收

    JVM内存区域模型: * 程序计数器,内存区域极小,是当前线程的字节码执行行号指示器: * 虚拟机栈.本地方法栈,即平时所说的“栈”,是虚拟机用来执行方法(包括Java.非Java方法)时,使用的临时 ...

  3. 最简单例子图解JVM内存分配和回收

    一.简介 JVM采用分代垃圾回收.在JVM的内存空间中把堆空间分为年老代和年轻代.将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象.年轻代中又被分 ...

  4. java虚拟机的内存分配与回收机制

    分为4个方面来介绍内存分配与回收,分别是内存是如何分配的.哪些内存需要回收.在什么情况下执行回收.如何监控和优化GC机制. java GC(Garbage Collction)垃圾回收机制,是java ...

  5. Android的内存分配与回收

    想写一篇关于android的内存分配和回收文章的想法来源于追查一个魅族手机图片滑动卡顿问题,我们想了很多办法还是没有避免他不停的GC,所以就打算详细的看看内存分配和GC的原理,为什么会不断的GC,GC ...

  6. Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法

    在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...

  7. 最简单例子图解JVM内存分配和回收(转)

    本文转自http://ifeve.com/a-simple-example-demo-jvm-allocation-and-gc/ http://www.idouba.net/a-simple-exa ...

  8. Java基础-Java中的内存分配与回收机制

    Java基础-Java中的内存分配与回收机制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一. 二.

  9. JVM内存分配与回收

    1.内存分配与回收策略 内存自动管理:自动化的解决了对象内存分配和回收对象内存的问题. 一般在堆上分配对象,也可能经过JTI编译后间接在栈上分配. 主要分配在新生代的Eden区,如果启动了本地线程分配 ...

随机推荐

  1. Hadoop-1.1.2、HBase-0.94.7完全分布式集群结构

    爱的技术可以应用到实际生活生产,做艺术向往的东西不腻和音乐. 现将前期手里面的一个项目做一个大致的总结,与大家一起分享.交流.进步. 项目如今正在线上执行,项目名--基于Hadoop的数据分析综合管理 ...

  2. JavaScript编写了一个计时器

    初学JavaScript,用JavaScript编写了一个计时器. 设计思想: 1.借助于Date()对象,来不断获取时间点: 2.然后用两次时间点的毫秒数相减,算出时间差: 3.累加时间差,这样就能 ...

  3. MyEclipse2014 设备 checkstyle、PMD、findbugs 最简单的方法 详细说明

    最近的实验需要的代码审查和应用程序性能优化.在需求MyEclipse安装某些插件,由于如今的MyEclipse版本号和大多数教程的不一样了,一些安装选项也已经改变,所以安装起来非常费事,通过不断的尝试 ...

  4. 关于启动调试时,总是启动多个web端口的问题

    在一个解决方案中,经常包含多个web项目,当设置某一个web项目为起始项目后,启动调试,会发现其他的web项目的端口也会一起打开,显示在电脑的右下方,其实开启其他端口有时是没必要的,只是多占用了系统内 ...

  5. linux_coom _ Linux文件比较,文本文件的交集、差集与求差

    交集和差集操作在集合论相关的数学课上经常用到,不过,在Linux下 对文本进行类似的操作在某些情况下也很有用. comm命令 comm命令可以用于两个文件之间的 比较,它有一些选项可以用来调整输出,以 ...

  6. AppiumDriver java部分api

    getAppStrings() 默认系统语言对应的Strings.xml文件内的数据. getAppStrings(String language) 查找某一个语言环境对应的字符串文件Strings. ...

  7. hdu 3709 数字dp(小思)

    http://acm.hdu.edu.cn/showproblem.php?pid=3709 Problem Description A balanced number is a non-negati ...

  8. Metrics监控应用

    使用Metrics监控应用程序的性能   在编写应用程序的时候,通常会记录日志以便事后分析,在很多情况下是产生了问题之后,再去查看日志,是一种事后的静态分析.在很多时候,我们可能需要了解整个系统在当前 ...

  9. IntelliJ IDEA 开发scala

    1.下载安装IntelliJ IDEA,并安装scala插件 我下载的是linux的13版本,linux版本是绿色版本,有一个启动的脚本,运行就可以了,也可以在linux建立快捷方式.windows的 ...

  10. CSharp设计模式读书笔记(6):建造者模式(学习难度:★★★★☆,使用频率:★★☆☆☆)

    建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 模式角色与模式: 建造者模式可以将一个产品的内部表象与产品的生产过程分割开来, ...