memcached分析详解目录1.文档目的 (1)1.1.前言 (1)2.memcached是什么 (2)2.1. memcached的特征 (2)3.memcached适合的场合 (4)4.memcached的代码分析 (5)4.1. main流程 (5)4.2. memcached服务流程(TCP) (6)4.3. memcached状态转换和通信协议处理 (7)4.4. memcached核心数据结构 (7)4.5. Slab Allocation机制:整理内存以便重复使用 (8)5.memcached的使用优化 (10)5.1. 命中率 (10)5.2. 空间利用率 (11)5.3. 加速比 (12)5.4. 安全性能 (12)6.memcached的测试分析 (13)6.1. 读写memcache指令测试 (13)6.2. 服务端系统负载 (13)6.3. 空间分配,命中率 (14)7.memcached的中间层客户端编写 (16)8.libevent简介 (17)9.memcached应用 (18)10.结束语 (20)1. 文档目的1.1. 前言文档就是简单的把memcached做一个代码走读和分析,起到一个抛砖引玉的作用;目的就是让大家在使用memcached这个工具时,多一些对工具的了解,从而确定你的程序是否真的需要用memcached来实现不可;短短2个小时也讲不了多少,主要是做一个学习探讨,如果大家感兴趣的话后期可以再做培训牛人真多啊,向先行者致敬!2. memcached是什么memcached广泛应用在大负载高并发的网站上,是一种非常成熟的产品(称为一项技术也未尝不可)。
像facebook,youtube,yahoo,sina,sohu,netease,豆瓣等网站均或多或少使用了该项产品。
memcached在以用户为中心的网站上,表现尤其突出,例如sns,blog等web2.0应用的站点。
这些站点一般来讲,特别注重用户体验,用户对服务器的响应速度要求很高,用户数据相对比较复杂、关连度比较高,需要经常对数据库进行更新和检索。
许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。
但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、网站显示延迟等重大影响。
这时就该memcached大显身手了。
memcached是高性能的分布式内存缓存服务器。
一般使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。
2.1. memcached的特征1)memcached的服务器客户端通信并不使用复杂的XML等格式,而使用简单的基于文本行的协议。
因此,通过telnet 也能在memcached上保存数据、取得数据。
下面是例子。
$ telnet localhost 8119Trying 127.0.0.1...Connected to localhost.localdomain (127.0.0.1).Escape character is '^]'.set foo 0 0 3 (保存命令)bar (数据)STORED (结果)get foo (取得命令)V ALUE foo 0 3 (数据)bar (数据)协议可以参考:2)为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。
由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。
另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。
memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。
3)memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。
各个memcached不会互相通信以共享信息。
那么,怎样进行分布式呢?这完全取决于客户端的实现。
3. memcached适合的场合memcached是“分布式”的内存对象缓存系统,那么就是说,那些不需要“分布”的,不需要共享的,或者干脆规模小到只有一台服务器的应用,memcached不会带来任何好处,相反还会拖慢系统效率,因为网络连接同样需要资源,即使是UNIX/Windows本地连接也一样。
测试数据显示,memcached本地读写速度要比直接.NET内存数组慢几十倍,而APC、共享内存方式都和直接数组差不多。
可见,如果只是本地级缓存,使用memcached是非常不划算的。
Memcached在很多时候都是作为数据库前端cache使用的。
因为它比数据库少了很多SQL解析、磁盘操作等开销,而且它是使用内存来管理数据的,所以它可以提供比直接读取数据库更好的性能,在大型系统中,访问同样的数据是很频繁的,memcached可以大大降低数据库压力,使系统执行效率提升。
另外,memcached 也经常作为服务器之间数据共享的存储媒介,例如在SSO系统中保存系统单点登陆状态的数据就可以保存在memcached中,被多个应用共享。
需要注意的是,memcached使用内存管理数据,所以它是易失的,当服务器重启,或者memcached进程中止,数据便会丢失,所以 memcached不能用来持久保存数据。
很多人的错误理解,memcached的性能非常好,好到了内存和硬盘的对比程度,其实memcached使用内存并不会得到成百上千的读写速度提高,它的实际瓶颈在于网络连接,它和使用磁盘的数据库系统相比,好处在于它本身非常“轻”,因为没有过多的开销和直接的读写方式,它可以轻松应付非常大的数据交换量,所以经常会出现两条千兆网络带宽都满负荷了,memcached进程本身并不占用多少CPU资源的情况。
4. memcached的代码分析4.1. main流程4.2. memcached服务流程(TCP)4.3. memcached状态转换和通信协议处理需要说明的是,这里需要排除所有出错处理.很显然,不管是哪种操作下,一旦出错,信息需要通过conn_write状态往client写入出错信息的,那么在string_out时,必定转入conn_write状态.而且,很多细节也没有在流程图中给出,如统计信息的处理,超时后get操作时删除等等.对于在memcache协议中定义的其他操作,如stats,version,quit,flush_all,own,disown等等由于使用很少,在流程中没有详细给出,可以查看源代码.4.4. memcached核心数据结构1. item结构item是存储在memcache的key-value对的抽象.由于组织item存放是按照LRU算法组织的.那么在其中有几个成员在修改源代码时必须注意,time是最近访问时间.exptime是item消亡时间.item是一个双向列表.同时还挂在一个Hash table上.2. conn结构conn结构是联系上下文的关键对象.对于每个连接的到来,都有一个conn结构与其对应,并且对应到某个连接状态,进入状态转换而完成操作.conn在程序开始也进行了一次预分配,分配200个连接空间.当200个使用完之后便是按需分配,到达一个分配一个.conn和item,iovec(内核级别缓冲结构)关联.3. slabclass_t结构slabclass_t保存了分级大小的空间槽,以分别适用于不同大小的item存放.取决于两个命令行参数,-f和-n.在应用 slabclass_t时,定义的是一个数组,该数组长度取决于增长的指数级别和初始值大小(32+chunk_size),每个空间槽是不允许大于1M 的,也就是1048576.4. settings结构系统获取的命令行参数保存的地方.5. stats结构:统计信息保存地方,可以考虑对其进行操作以适应不同的统计信息处理,如获取某个时间段的get 命中率等等操作.4.5. Slab Allocation机制:整理内存以便重复使用1)memcached默认情况下采用了名为Slab Allocator的机制分配、管理内存。
在该机制出现以前,内存的分配是通过对所有记录简单地进行malloc和free来进行的。
但是,这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下,会导致操作系统比memcached进程本身还慢。
Slab Allocation的原理相当简单。
将分配的内存分割成各种尺寸的块(chunk),并把尺寸相同的块分成组(chunk的集合)(图1)。
2)memcached根据收到的数据的大小,选择最适合数据大小的slab(图2)。
memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk,然后将数据缓存于其中。
3)Slab Allocator解决了当初的内存碎片问题,但新的机制也给memcached带来了新的问题。
这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。
例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了(图3)。
4)memcached在启动时指定Growth Factor因子(通过-f选项),就可以在某种程度上控制slab之间的差异。
默认值为1.25。
但是,在该选项出现之前,这个因子曾经固定为2,称为“powers of 2”策略。
将memcached引入产品,或是直接使用默认值进行部署时,最好是重新计算一下数据的预期平均长度,调整growth factor,以获得最恰当的设置。
内存是珍贵的资源,浪费就太可惜了。
5. memcached的使用优化在优化memcache的工作之前,需要了解memcache体系的工作流程.一个分布式的memcache的运作是需要三个部分的,多台提供 memcache服务的servers(简称S),一个进行分布式映射的中间层lib(其实这个也可以当作客户端的一部分,简称L),和进行 memcache请求的客户端(简称C).在memcache工作时,有以下四个步骤:1. C通过带有特性化的Key值的命令串,向L请求memcache服务,L将命令串进行分解,并通过对key的某种Hash算法决定S的地址2. L将分解的(Comm Key-Value)或者(Comm Key)向相关的S请求memcache服务.3. 相关的S根据memcache协议向L返回服务结果.4. L将结果进行聚集包装后返回给C一个人性化的响应.从上面的分析可以看出,分布式的memcache服务是需要很大的网络开销的.对于一般的应用而言,是否都要进行memcache的优化,甚至是否需要用到memcache,都需要进行权衡分析.如果只是本地小规模的应用,在数据库可承受的范围内,是宁愿采用数据库+文件缓存的方式.1.1版本的 memcache走TCP模式在本地应用的处理速度甚至比不上Mysql数据的Unix域套接口的处理速度的一半,当然会更加比不上在程序内部直接操作内存了.虽然1.2版本的memcache已经提供了-s参数指定Unix域套口和-u指定udp模式.而且如果不需要用到分布式的话,不推荐使用 memcache,除非你的内存足够大到浪费的程度.优化可以从以下几个方面进行:5.1. 命中率对于缓存服务而言,命中率是至关重要的.命中率的提升可以通过多种方案实现.其一,提高服务获取的内存总量.这无疑是增加命中的最直接的办法,将缓存数据完全放入数据池中.只要连接不失效,就一定命中.其二,提高空间利用率,这实际上也是另一种方式的增加内存总量.具体实现在下一个方面给出.其三,对于一些很特别的memcache应用,可以采用多个memcache服务进行侦听,分开处理,针对服务提供的频繁度划分服务内存,相当于在应用一级别上再来一次LRU.其四,对于整体命中率,可以采取有效的冗余策略,减少分布式服务时某个server发生服务抖动的情况.如,14台机器实现分布式memcache,划分两组服务,其中一组13台做一个分布式的memcache,一组1台做整个的memcache备份.对于update操作,需要进行两边,get操作只需要一遍,一旦访问失效,则访问备份服务器.这样,对于备份服务器需要内存比较大,而且只适应于读操作大于写操作的应用中.这可以认为是RAID3,当然,也可以采用RAID1完全镜像.5.2. 空间利用率对于使用memcache做定长数据缓存服务而言,是可以在空间利用率上进行优化.甚至最简单的办法可以不用更改memcache的源码遍可以完成由 -f和-n参数的配合可以做到定长优化,不过极可能需要浪费掉预分配的199M内存空间.当然前提是memcache的版本是1.2,同时如果使用的是 1.2.0和1.2.1的话,需要更改掉一个BUG,那就是getopt时将opt串中最后一个”s”改成”n”,希望memcache能在以后的版本发现这个BUG.例如,如果key是一个定长id(如一个8位的流水号00000001),value是一个定长的串(如16位的任意字符串),对应于一个 chunk_size可以这么计算:chunk_size = sizeof(item) + nkey + *nsuffix + nbytes = 32 + 9 + (flag的数位长度 )2+ (16)的数位长度) 2+(两换行的长度)4 + 17 = 40 + 10 + 16 = 66,那么可以通过 -f 1.000001 -n `expr 66 - 32`,即 -f 1.000001 -n 34 来启动memcache.这种情况下,会浪费掉memcache预先分配的200M空间中的199M.从第2个预分配等级到第200个预分配等级将不会用到.然而,存在解决办法,那就是在编译memcache是加入编译参数-DDONT_PREALLOC_SLABS,或者在源代码中加入#define DONT_PREALLOC_SLABS即可,只是会去除memcache的预分配内存机制.如果需要memcache的预分配内存机制,那么需要对其源代码进行修改.修改如下:引用1. 在slabs.c中,将函数slabs_clsid改成:unsigned int slabs_clsid(size_t size){ unsigned int res = POWER_SMALLEST;if(size==0)return 0;res = (size)%power_largest;return res;}2. 在item.c中,将函数 item_make_header改为:int item_make_header(char *key, uint8_t nkey, int flags, int nbytes,char *suffix, int *nsuffix){*nsuffix = sprintf(suffix, " %u %u\r\n", flags, nbytes - 2);return sizeof(item)+ nkey + *nsuffix + nbytes + hash(key,nkey,0);}3. 在item.c中,将函数 item_free改为:void item_free(item *it){ unsigned int ntotal = it->slabs_clsid;assert((it->it_flags & ITEM_LINKED) == 0);assert(it != heads[it->slabs_clsid]);assert(it != tails[it->slabs_clsid]);assert(it->refcount == 0);it->slabs_clsid = 0;it->it_flags |= ITEM_SLABBED;slabs_free(it, ntotal);}做一个轮流存储的机制使用预分配的内存,这样的好处是其他地方不需要做任何修改就可以了,当然你可以在源代码中加入上面的代码,并将它们放在一个自定义的宏后面.5.3. 加速比加速比,也即事件的处理效率.是否可以修改libevent的事件处理效率,需要研究.如果内存空间很大,可以将freeconn的数值调大,增加预分配的conn内存大小.5.4. 安全性能memcache还存在一个比较显著的问题,那就是其安全性能.只要了解memcache监听的端口,对于能够使用分布式memcache进行数据通信的网络环境的机器,都可以通过memcache协议于memcache服务器进行通信,获取或种植数据.不能保证种植进内存里的数据不会被别有用心的人再利用.也不能保证服务器的内存不被漫天遍地的垃圾数据所堆积,造成命中极低.memcache的设计理念在一个轻字,如果对每次Client的通讯需要校验身份,那么恐怕memcache也就达不到其想要的效果了.存在解决办法缓解这个问题,一般而言,需要使用memcache服务的机器,可以在Server维持一张红色列表.这张表上的机器便可以获取服务.很显然,memcache并非任意Client都能访问,只有信任的机器访问,那么为什么不将这些信任的机器放在一个/etc/mem_passwd下呢.还有,memcached走udp时,很大几率接受到upd时,都会使服务死掉,特别是set,add,replace时,这个问题需要去考究一下.不过没有时间了.6. memcached的测试分析服务器端memcache在命令行运行的参数:memcached –d –m 512 –l *.*.*.* -u ** -f 1.00001 –n 16 –c 10000 –vv6.1. 读写memcache指令测试6.2. 服务端系统负载通过自己编写的服务器端,对单结点的memcache进行了连接压力测试.其中测试用例的编写是这样的:启用七个客户端,每个客户端串行运行1000个进程,每个进程开3000线程,每个线程执行10次memcache的读操作或者写操作(操作相同).客户端并发连接.1. 客户端(7)的环境:Intel(R) Xeon(R) CPU 5120 @ 1.86GHz,4G memory.2. 服务器端(1)的环境:Intel(R) Xeon(R) CPU 5120 @ 1.86GHz,4G memory.3. 网络环境:100M网卡,Cisco交换机.很显然,memcache的运行在系统cpu的消耗上占十分少的比重,即便是很恐怖的并发连接也不会给系统带来多大的负载,因为其磁盘IO free(所有操作都在内存中)和相应的内存分配机制决定其占用cpu的极少,而相反,在网络IO上却花费很大的时间.6.3. 空间分配,命中率由于本地测试的get数据非常固定,因此命中率基本为100%.在10.68.1.31上运行了一个有前端应用的memcachce服务器,运行时间已经有364个多小时了.因此通过10.68.1.31上的数据说明(版本为1.1.13).通过memcache的统计协议可以清楚的看到其命中率高达95.9%7. memcached的中间层客户端编写省略8. libevent简介libevent是一个事件触发的网络库,适用于windows,linux,bsd等多种平台,内部使用iopc/epoll/kqueue 等系统调用管理事件机制,而且根据libevent官方网站上公布的数据统计,似乎也有着非凡的性能.从代码中看,libevent支持用户使用三种类型的事件,分别是网络IO,定时器,信号三种,在定时器的实现上使用了红黑树(RB tree)的数据结构,以达到高效查找,排序,删除定时器的目的,网络IO 上,libevent的epoll居然用的EPOLLLT水平触发的方式,不容易出错,但是在效率上可能比EPOLLET要低一些.跟网络无关的,libevent也有一些缓冲区管理的函数,libevent没有提供缓存的函数.而且libevent 的接口形式非常值得参考.9. memcached应用原文:Scaling memcached at Facebook作者:Paul Saab翻译:ShiningRay如果你翻阅过一些关于大型网站扩展(Scaling)的资料,那么你可能听说过一个叫memcached的东西。