admin 管理员组

文章数量: 1184232


2024年3月20日发(作者:sumproduct多条件求和返回错误)

memcached分析详解

修订日期

2009-3-13

修订内容

初始化

版本

修订人

江军

目 录

1.

2.

3.

4.

文档目的 ............................................................................................................................................................ 1

1.1.

前言 ...................................................................................................................................................... 1

memcached是什么 ............................................................................................................................................ 2

2.1. memcached的特征....................................................................................................................................... 2

memcached适合的场合 ..................................................................................................................................... 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.

8.

9.

10.

memcached的中间层客户端编写 ................................................................................................................... 16

libevent简介 ................................................................................................................................................. 17

memcached应用 ............................................................................................................................................... 18

结束语 ..................................................................................................................................................... 20

1. 文档目的

1.1. 前言

文档就是简单的把memcached做一个代码走读和分析,起到一个抛砖引玉的作用;

目的就是让大家在使用memcached这个工具时,多一些对工具的了解,从而确定你的程序是否真的需要用

memcached来实现不可;

短短2个小时也讲不了多少,主要是做一个学习探讨,如果大家感兴趣的话后期可以再做培训

牛人真多啊,向先行者致敬!

1 / 9

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 8119

Trying 127.0.

Connected to omain (127.0.0.1).

Escape character is '^]'.

set foo 0 0 3 (保存命令)

bar (数据)

2 / 9

STORED (结果)

get foo (取得命令)

VALUE foo 0 3 (数据)

bar (数据)

协议可以参考:

2)为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。 由于数据仅存

在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。 另外,内容容量达到指定值之后,

就基于LRU(Least Recently Used)算法自动删除不使用的缓存。 memcached本身是为缓存而设计的服务器,

因此并没有过多考虑数据的永久性问题。

3)memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通

信以共享信息。那么,怎样进行分布式呢? 这完全取决于客户端的实现。

3 / 9

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 / 9

4. memcached的代码分析

4.1. main流程

5 / 9

4.2. memcached服务流程(TCP)

6 / 9

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结构

7 / 9

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, 然后将数据缓存于其中。

8 / 9

3)Slab Allocator解决了当初的内存碎片问题,但新的机制也给memcached带来了新的问题。

这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。 例如,将100字节的数据

缓存到128字节的chunk中,剩余的28字节就浪费了(图3)。

4)memcached在启动时指定 Growth Factor因子(通过-f选项), 就可以在某种程度上控制slab之间的差

异。默认值为1.25。但是,在该选项出现之前,这个因子曾经固定为2,称为“powers of 2”策略。

将memcached引入产品,或是直接使用默认值进行部署时, 最好是重新计算一下数据的预期平均长度,

调整growth factor, 以获得最恰当的设置。内存是珍贵的资源,浪费就太可惜了。

9 / 9

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. 命中率

对于缓存服务而言,命中率是至关重要的.命中率的提升可以通过多种方案实现.其一,提高服务获取的内存总

10 / 9

量.这无疑是增加命中的最直接的办法,将缓存数 据完全放入数据池中.只要连接不失效,就一定命中.其二,提

高空间利用率,这实际上也是另一种方式的增加内存总量.具体实现在下一个方面给出.其三,对于 一些很特

别的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 %urn", 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;

11 / 9

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时,

这个问题需要去考究一下.不过没有时间了.

12 / 9

6. memcached的测试分析

服务器端memcache在命令行运行的参数:

memcached –d –m 512 –l *.*.*.* -u ** -f 1.00001 –n 16 –c 10000 –vv

6.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交换机.

13 / 9

很显然,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%

14 / 9

15 / 9

7. memcached的中间层客户端编写

省略

16 / 9

8. libevent简介

libevent是一个事件触发的网络库,适用于windows,linux,bsd等多种平台,内部使用iopc/epoll/kqueue

等系统调用管理事件机制,而且根据libevent官方网站上公布的数据统计,似乎也有着非凡的性能.

从代码中看,libevent支持用户使用三种类型的事件,分别是网络IO,定时器,信号三种,在定时器

的实现上使用了红黑树(RB tree)的数据结构,以达到高效查找,排序,删除定时器的目的,网络IO

上,libevent的epoll居然用的EPOLLLT水平触发的方式,不容易出错,但是在效率上可能比EPOLLET要低

一些.跟网络无关的,libevent也有一些缓冲区管理的函数,libevent没有提供缓存的函数.而 且libevent

的接口形式非常值得参考.

17 / 9

9. memcached应用

原文:Scaling memcached at Facebook

作者:Paul Saab

翻译:ShiningRay

如果你翻阅过一些关于大型网站扩展(Scaling)的资料,那么你可能听说过一个叫memcached的东西。

memcached是一个高性能、 分布式的内存对象缓存系统。我们Facebook可能是世界上最大的memcached

用户了。我们利用memcached来减轻数据库的负担。 memcached确实很快,但是我们还要让他更快、更高

效。我们使用了超过800台服务器,提供超过28TB的内存来服务于用户。在过去的一年里,随着 Facebook

的用户量直线上升,我们遇到了一系列的扩展问题。日益增长的需求使得我们必须对操作系统和memcached

进行一些修改,以获得足够的 性能来为我们的用户提供最好的体验。

因为我们有好几千台机器,每个都运行了几百个Apache进程甚至更多,最终导致到memcached进程的TCP

链接有几十万个。这些链接本身并不是什 么大问题,但是memcached为每个TCP链接分配内存的方法却很

成问题。memcached为每个链接使用单独的缓存进行数据的读写。当达到几十万 链接的时候,这些累计起

来达好几个G——这些内存其实可以更好地用于存储用户数据。为了收复这些内存,我们实现了一个针对

TCP和UDP套接字的每线程共享的链接缓存池。这个改变使每个服务器可以收回几个G的内存。

虽然TCP上我们改进了内存的使用效率,但我们还是转向了UDP,目的是让get(获取)操作能降低网络流

量、让multi-get(同时并行地获取几百 个键值)能实现应用程序级别的流量控制。我们发现Linux上到

了一定负载之后,UDP的性能下降地很厉害。这是由于,当从多个线程通过单个套接字传递数 据时,在UDP

套接字锁上产生的大量锁竞争导致的。要通过分离锁来修复内核恐怕不太容易。所以,我们使用了分离的

UDP套接字来传递回复(每个线程用一个答复套接字)。这样改动之后,我们就可以部署UDP同时后端性能

不打折。

另一个Linux中的问题是到了一定负载后,某个核心可能因进行网络软终端处理会饱和而限制了网络IO。

在Linux中,网络中断只会总是传递给某个核心,因此所有的接受软终端的网络处理都发生在该内核上。

另外,我们还发现某些网卡有过高的中断频率。我们通过引入网络接口的“投机”轮询解决了这两个问题。

在该模型中,我们组合了中断驱动和轮询驱动的网络IO。一旦进入网络驱动(通常是传输一个数据包时)

以及在进程调度器的空闲循环的时候,对网络接口进行轮询。另外,我们也用到了中断(来控制延迟),不

过网络中断用到的数量大大减少(一般通过大幅度提升中断联结阈值interrupt coalescing thresholds)。

由于我们在每个核心上进行网络传输,同时由于在调度器的空闲循环中对网络IO进行轮询,我们将网络处

理均匀地分散到每个核心上。

最后,当开始部署8核机器的时候,我们在测试中发现了新的瓶颈。首先,memcached的stat工具集依赖

于一个全局锁。这在4核上已经很令人讨厌 了,在8核上,这个锁可以占用20-30%的CPU使用率。我们

通过将stats工具集移入每个线程,并且需要的时候将结果聚合起来。其次,我们发现随着 传递UDP数据

包的线程数量的增加,性能却在降低。最后在保护每个网络设备的传送队列的锁上发现了严重的争用。数

据包是由设备驱动进行入队传输和出队。该队列由Linux的“netdevice”层来管理,它位于IP和设备驱

动之间。每次只能有一个数据包加入或移出队列,这造成了严重的争用。我们当中的一 位工程师修改了出

队算法,实现了传输的批量出队,去掉了队列锁,然后批量传送数据包。这个更正将请求锁的开销平摊到

了多个数据包,显著地减少了锁争用,这 样我们就能在8核系统上将memcached伸展至8线程。

做了这些修改之后,我们可以将memcached提升到每秒处理20万个UDP请求,平均延迟降低为173微秒。

可以达到的总吞吐量为30万UDP请求 /s,不过在这个请求速度上的延迟太高,因此在我们的系统中用处

不大。对于普通版本的Linux和memcached上的50,000 UDP请求/s而言,这是个了不起的提升。

我们希望尽快将我们的修改集成到官方的memcached仓库中去,我们决定在这之前,先将我们对memcached

18 / 9

的修改发布到github上。

19 / 9

10. 结束语

重新审视现在的体系

memcached支持外部存储的难点是,网络和事件处理相关的代码(核心服务器)与 内存存储的代码紧密关

联。这种现象也称为tightly coupled(紧密耦合)。 必须将内存存储的代码从核心服务器中独立出来,

才能灵活地支持外部引擎。 因此,基于我们设计的API,memcached被重构成下面的样子:

重构之后,我们与1.2.5版、二进制协议支持版等进行了性能对比,证实了它不会造成性能影响。

在考虑如何支持外部引擎加载时,让memcached进行并行控制(concurrency control)的方案是最为容易

的, 但是对于引擎而言,并行控制正是性能的真谛,因此我们采用了将多线程支持完全交给引擎的设计方

案。

以后的改进,会使得memcached的应用范围更为广泛。

20 / 9


本文标签: 内存 数据 进行 使用 需要