memcached总结

memcached总结

 

目录

memcached总结... 1

一、Memcached的安装... 3

1、安装环境... 3

2、安装方法... 3

3、验证是否安装成功... 3

4、查看配置文件... 3

5、启动memcached. 4

6、Telnet客户端... 6

二、memcached的内存存储... 10

1、      Slab Allocation机制... 10

2、      Slab Allocation的主要术语... 10

3、      Slab中缓存记录的原理... 11

4、      Slab Allocator的缺点... 11

5、      使用Growth Factor进行调优... 12

6、      查看slabs的使用状况... 13

三、memcached的删除机制和发展方向... 14

1、      从缓存中有效删除数据的原理... 14

2、      memcached的最新发展方向... 14

3、      关于二进制协议... 15

4、      二进制协议的格式... 15

5、      HEADER中引人注目的地方... 17

四、memcached的分布式算法... 18

1、      memcached的分布式... 18

2、      memcached的分布式是什么意思?... 18

3、      Cache::Memcached的分布式方法... 20

4、      根据余数计算分散... 20

5、      根据余数计算分散的缺点... 21

五、Consistent Hashing. 23

1、      Consistent Hashing的简单说明... 23

六、memcached的应用和兼容程序... 25

1、      mixi案例研究... 25

2、      服务器配置和数量... 25

3、      memcached进程... 26

4、      memcached使用方法和客户端... 26

5、      通过Cache::Memcached::Fast维持连接... 26

6、      公共数据的处理和rehash. 27

七、memcached应用经验... 28

1、      通过daemontools启动... 28

2、      监视... 28

3、      memcached的性能... 29

 

 

 

 

 

 

 

 

 

一、Memcached的安装

1、安装环境

安装环境:CentOs 7

2、安装方法

yum -y install memcached

3、验证是否安装成功

memcached –h

 

4、查看配置文件

cat /etc/sysconfig/memcached

可以根据情况修改相关配置参数:

PORT="11211"

USER="memcached"

MAXCONN="1024"

CACHESIZE="64"

5、启动memcached

/etc/rc.d/init.d/memcachserver start

 

启动时可选参数说明:

 

选项

含义说明

-d

指定memcached进程作为一个守护进程启动

-m <num>

指定分配给memcached使用的内存,单位是MB

-u <username>

运行memcached的用户

-l <ip_addr>

监听的服务器IP地址,如果有多个地址的话,使用逗号分隔,格式可以为“IP地址:端口号”,例如:-l 指定192.168.0.184:19830,192.168.0.195:13542;端口号也可以通过-p选项指定

-p <num>

Memcached监听的端口,要保证该端口号未被占用

-c <num>

设置最大运行的并发连接数,默认是1024

-R <num>

为避免客户端饿死(starvation),对连续达到的客户端请求数设置一个限额,如果超过该设置,会选择另一个连接来处理请求,默认为20

-k

设置锁定所有分页的内存,对于大缓存应用场景,谨慎使用该选项

-P

保存memcached进程的pid文件

-s <file>

指定Memcached用于监听的UNIX socket文件

-a <perms>

设置-s选项指定的UNIX socket文件的权限

-U <num>

指定监听UDP的端口,默认11211,0表示关闭

-M

当内存使用超出配置值时,禁止自动清除缓存中的数据项,此时Memcached不可以,直到内存被释放

-r

设置产生core文件大小

-f <factor>

用于计算缓存数据项的内存块大小的乘数因子,默认是1.25

-n

为缓存数据项的key、value、flag设置最小分配字节数,默认是48

-C

禁用CAS

-h

显示Memcached版本和摘要信息

-v

输出警告和错误信息

-vv

打印信息比-v更详细:不仅输出警告和错误信息,也输出客户端请求和响应信息

-i

打印libevent和Memcached的licenses信息

-t <threads>

指定用来处理请求的线程数,默认为4

-D <char>

用于统计报告中Key前缀和ID之间的分隔符,默认是冒号“:”

-L

尝试使用大内存分页(pages)

-B <proto>

指定使用的协议,默认行为是自动协商(autonegotiate),可能使用的选项有auto、ascii、binary。

-I <size>

覆盖默认的STAB页大小,默认是1M

-F

禁用flush_all命令

-o <options>

指定逗号分隔的选项,一般用于用于扩展或实验性质的选项

 

查看是否启动:ps -aux | grep memcached

 

 

停止服务:/etc/rc.d/init.d/memcached stop

6、Telnet客户端

Telnet客户端可以通过命令行的方式来监控查看Memcached服务器存储数据的情况。例如,Memcached的服务地址为127.0.0.1 11211,可以telnet到该服务端口:

telnet 192.168.4.86 11211

如果连接成功,可以使用如下一些命令:

stats命令

该命令用于显示服务器信息、统计数据等,结果示例数据,例如:

STAT pid 22362    //memcache服务器的进程ID  

STAT uptime 1469315    //服务器已经运行的秒数

STAT time 1339671194    //服务器当前的unix时间戳

STAT version 1.4.9    //memcache版本

STAT libevent 1.4.9-stable    //libevent版本

STAT pointer_size 64    //当前操作系统的指针大小(32位系统一般是32bit,64就是64位操作系统)

STAT rusage_user 3695.485200    //进程的累计用户时间

STAT rusage_system 14751.273465    //进程的累计系统时间

STAT curr_connections 69    //服务器当前存储的items数量

STAT total_connections 855430    //从服务器启动以后存储的items总数量

STAT connection_structures 74    //服务器分配的连接构造数

STAT reserved_fds 20    //

STAT cmd_get 328806688    //get命令(获取)总请求次数

STAT cmd_set 75441133    //set命令(保存)总请求次数 

STAT cmd_flush 34    //flush命令请求次数

STAT cmd_touch 0    //touch命令请求次数

STAT get_hits 253547177    //总命中次数

STAT get_misses 75259511    //总未命中次数

STAT delete_misses 4    //delete命令未命中次数

STAT delete_hits 565730    //delete命令命中次数

STAT incr_misses 0    //incr命令未命中次数

STAT incr_hits 0    //incr命令命中次数

STAT decr_misses 0    //decr命令未命中次数

STAT decr_hits 0    //decr命令命中次数

STAT cas_misses 0    //cas命令未命中次数

STAT cas_hits 0        //cas命令命中次数

STAT cas_badval 0    //使用擦拭次数

STAT touch_hits 0    //touch命令未命中次数

STAT touch_misses 0    //touch命令命中次数

STAT auth_cmds 0    //认证命令处理的次数

STAT auth_errors 0    //认证失败数目

STAT bytes_read 545701515844        //总读取字节数(请求字节数)

STAT bytes_written 1649639749866    //总发送字节数(结果字节数)

STAT limit_maxbytes 2147483648        //分配给memcache的内存大小(字节)

STAT accepting_conns 1            //服务器是否达到过最大连接(0/1

STAT listen_disabled_num 0    //失效的监听数

STAT threads 4        //当前线程数

STAT conn_yields 14    //连接操作主动放弃数目

STAT hash_power_level 16    //

STAT hash_bytes 524288

STAT hash_is_expanding 0

STAT expired_unfetched 30705763

STAT evicted_unfetched 0

STAT bytes 61380700    //当前存储占用的字节数

STAT curr_items 28786    //当前存储的数据总数

STAT total_items 75441133    //启动以来存储的数据总数

STAT evictions 0    //为获取空闲内存而删除的items数(分配给memcache的空间用满后需要删除旧的items来得到空间分配给新的items)

STAT reclaimed 39957976    //已过期的数据条目来存储新数据的数目

END

二、memcached的内存存储

 

 

1、   Slab Allocation机制

memcached默认情况下采用了名为Slab Allocator的机制分配、管理内存。 在该机制出现以前,内存的分配是通过对所有记录简单地进行malloc和free来进行的。 但是,这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下, 会导致操作系统比memcached进程本身还慢。Slab Allocator就是为解决该问题而诞生的。

 

Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块, 以完全解决内存碎片问题。

Slab Allocation的原理相当简单。 将分配的内存分割成各种尺寸的块(chunk), 
并把尺寸相同的块分成组(chunk的集合)(图1)。

 

图1 Slab Allocation的构造图

而且,slab allocator还有重复使用已分配的内存的目的。 也就是说,分配到的内存不会释放,而是重复利用。

2、   Slab Allocation的主要术语

Page

分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk。

Chunk

用于缓存记录的内存空间。

Slab Class

特定大小的chunk的组。

3、   Slab中缓存记录的原理

下面说明memcached如何针对客户端发送的数据选择slab并缓存到chunk中。

memcached根据收到的数据的大小,选择最适合数据大小的slab(图2)。 
memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk, 
然后将数据缓存于其中。

 

图2 选择存储记录的组的方法

实际上,Slab Allocator也是有利也有弊。下面介绍一下它的缺点。

4、   Slab Allocator的缺点

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

这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。 例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了(图3)。

 

图3 chunk空间的使用

就是说,如果预先知道客户端发送的数据的公用大小,或者仅缓存大小相同的数据的情况下, 只要使用适合数据大小的组的列表,就可以减少浪费。

但是很遗憾,现在还不能进行任何调优,只能期待以后的版本了。 但是,我们可以调节slab class的大小的差别。 接下来说明growth factor选项。

 

5、   使用Growth Factor进行调优

memcached在启动时指定 Growth Factor因子(通过-f选项), 
就可以在某种程度上控制slab之间的差异。默认值为1.25。 但是,在该选项出现之前,这个因子曾经固定为2,称为“powers of 2”策略。

让我们用以前的设置,以verbose模式启动memcached试试看:

$ memcached -f 2 -vv

下面是启动后的verbose输出:

slab class   1: chunk size    128 perslab  8192

slab class   2: chunk size    256 perslab  4096

slab class   3: chunk size    512 perslab  2048

slab class   4: chunk size   1024 perslab  1024

slab class   5: chunk size   2048 perslab   512

slab class   6: chunk size   4096 perslab   256

slab class   7: chunk size   8192 perslab   128

slab class   8: chunk size  16384 perslab    64

slab class   9: chunk size  32768 perslab    32

slab class  10: chunk size  65536 perslab    16

slab class  11: chunk size 131072 perslab     8

slab class  12: chunk size 262144 perslab     4

slab class  13: chunk size 524288 perslab     2

可见,从128字节的组开始,组的大小依次增大为原来的2倍。 这样设置的问题是,slab之间的差别比较大,有些情况下就相当浪费内存。 因此,为尽量减少内存浪费,两年前追加了growth factor这个选项。

来看看现在的默认设置(f=1.25)时的输出(篇幅所限,这里只写到第10组):

slab class   1: chunk size     88 perslab 11915

slab class   2: chunk size    112 perslab  9362

slab class   3: chunk size    144 perslab  7281

slab class   4: chunk size    184 perslab  5698

slab class   5: chunk size    232 perslab  4519

slab class   6: chunk size    296 perslab  3542

slab class   7: chunk size    376 perslab  2788

slab class   8: chunk size    472 perslab  2221

slab class   9: chunk size    592 perslab  1771

slab class  10: chunk size    744 perslab  1409

可见,组间差距比因子为2时小得多,更适合缓存几百字节的记录。 从上面的输出结果来看,可能会觉得有些计算误差, 这些误差是为了保持字节数的对齐而故意设置的。

将memcached引入产品,或是直接使用默认值进行部署时, 最好是重新计算一下数据的预期平均长度,调整growth factor, 以获得最恰当的设置。内存是珍贵的资源,浪费就太可惜了。

接下来介绍一下如何使用memcached的stats命令查看slabs的利用率等各种各样的信息。

 

6、   查看slabs的使用状况

使用方法也极其简单:

$ memcached-tool 主机名:端口 选项

查看slabs使用状况时无需指定选项,因此用下面的命令即可:

$ memcached-tool 主机名:端口

获得的信息如下所示:

 #  Item_Size   Max_age  1MB_pages Count   Full?

 1     104 B  1394292 s    1215 12249628    yes

 2     136 B  1456795 s      52  400919     yes

 3     176 B  1339587 s      33  196567     yes

 4     224 B  1360926 s     109  510221     yes

 5     280 B  1570071 s      49  183452     yes

 6     352 B  1592051 s      77  229197     yes

 7     440 B  1517732 s      66  157183     yes

 8     552 B  1460821 s      62  117697     yes

 9     696 B  1521917 s     143  215308     yes

10     872 B  1695035 s     205  246162     yes

11     1.1 kB 1681650 s     233  221968     yes

12     1.3 kB 1603363 s     241  183621     yes

13     1.7 kB 1634218 s      94   57197     yes

14     2.1 kB 1695038 s      75   36488     yes

15     2.6 kB 1747075 s      65   25203     yes

16     3.3 kB 1760661 s      78   24167     yes

各列的含义为:

含义

#

slab class编号

Item_Size

Chunk大小

Max_age

LRU内最旧的记录的生存时间

1MB_pages

分配给Slab的页数

Count

Slab内的记录数

Full?

Slab内是否含有空闲chunk

从这个脚本获得的信息对于调优非常方便,强烈推荐使用。

 

三、memcached的删除机制和发展方向

1、   从缓存中有效删除数据的原理

memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况, 
此时就要使用名为 Least Recently Used(LRU)机制来分配空间。 顾名思义,这是删除“最近最少使用”的记录的机制。 因此,当memcached的内存空间不足时(无法从slab class 获取到新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。 从缓存的实用角度来看,该模型十分理想。

不过,有些情况下LRU机制反倒会造成麻烦。memcached启动时通过“-M”参数可以禁止LRU,如下所示:

$ memcached -M -m 1024

启动时必须注意的是,小写的“-m”选项是用来指定最大内存大小的。不指定具体数值则使用默认值64MB。

指定“-M”参数启动后,内存用尽时memcached会返回错误。 话说回来,memcached毕竟不是存储器,而是缓存,所以推荐使用LRU。

2、   memcached的最新发展方向

memcached的roadmap上有两个大的目标。一个是二进制协议的策划和实现,另一个是外部引擎的加载功能。

3、   关于二进制协议

使用二进制协议的理由是它不需要文本协议的解析处理,使得原本高速的memcached的性能更上一层楼, 还能减少文本协议的漏洞。目前已大部分实现,开发用的代码库中已包含了该功能。 memcached的下载页面上有代码库的链接。

4、   二进制协议的格式

协议的包为24字节的帧,其后面是键和无结构数据(Unstructured Data)。 实际的格式如下(引自协议文档):

 Byte/     0       |       1       |       2       |       3       |   
    /              |               |               |               |   
   |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
   +---------------+---------------+---------------+---------------+
  0/ HEADER                                                        /   
   /                                                               /   
   /                                                               /   
   /                                                               /   
   +---------------+---------------+---------------+---------------+
 24/ COMMAND-SPECIFIC EXTRAS (as needed)                           /   
  +/  (note length in th extras length header field)               /   
   +---------------+---------------+---------------+---------------+
  m/ Key (as needed)                                               /   
  +/  (note length in key length header field)                     /   
   +---------------+---------------+---------------+---------------+
  n/ Value (as needed)                                             /   
  +/  (note length is total body length header field, minus        /   
  +/   sum of the extras and key length body fields)               /   
   +---------------+---------------+---------------+---------------+
  Total 24 bytes

如上所示,包格式十分简单。需要注意的是,占据了16字节的头部(HEADER)分为 请求头(Request Header)和响应头(Response Header)两种。 头部中包含了表示包的有效性的Magic字节、命令种类、键长度、值长度等信息,格式如下:

Request Header
 
 Byte/     0       |       1       |       2       |       3       |
    /              |               |               |               |
   |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
   +---------------+---------------+---------------+---------------+
  0| Magic         | Opcode        | Key length                    |
   +---------------+---------------+---------------+---------------+
  4| Extras length | Data type     | Reserved                      |
   +---------------+---------------+---------------+---------------+
  8| Total body length                                             |
   +---------------+---------------+---------------+---------------+
 12| Opaque                                                        |
   +---------------+---------------+---------------+---------------+
 16| CAS                                                           |
   |                                                               |
   +---------------+---------------+---------------+---------------+
 
Response Header
 
 Byte/     0       |       1       |       2       |       3       |
    /              |               |               |               |
   |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
   +---------------+---------------+---------------+---------------+
  0| Magic         | Opcode        | Key Length                    |
   +---------------+---------------+---------------+---------------+
  4| Extras length | Data type     | Status                        |
   +---------------+---------------+---------------+---------------+
  8| Total body length                                             |
   +---------------+---------------+---------------+---------------+
 12| Opaque                                                        |
   +---------------+---------------+---------------+---------------+
 16| CAS                                                           |
   |                                                               |
   +---------------+---------------+---------------+---------------+

如希望了解各个部分的详细内容,可以checkout出memcached的二进制协议的代码树, 参考其中的docs文件夹中的protocol_binary.txt文档。

5、   HEADER中引人注目的地方

看到HEADER格式后我的感想是,键的上限太大了!现在的memcached规格中,键长度最大为250字节, 但二进制协议中键的大小用2字节表示。因此,理论上最大可使用65536字节(216)长的键。 尽管250字节以上的键并不会太常用,二进制协议发布之后就可以使用巨大的键了。

二进制协议从下一版本1.3系列开始支持。

 

 

 

四、memcached的分布式算法

1、   memcached的分布式

 memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能。 
服务器端仅包括前面介绍的内存存储功能,其实现非常简单。 至于memcached的分布式,则是完全由客户端程序库实现的。 这种分布式是memcached的最大特点。

2、   memcached的分布式是什么意思?

这里多次使用了“分布式”这个词,但并未做详细解释。 现在开始简单地介绍一下其原理,各个客户端的实现基本相同。

下面假设memcached服务器有node1~node3三台, 应用程序要保存键名为“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的数据。

 

图1 分布式简介:准备

首先向memcached中添加“tokyo”。将“tokyo”传给客户端程序库后, 客户端实现的算法就会根据“键”来决定保存数据的memcached服务器。 服务器选定后,即命令它保存“tokyo”及其值。

 

图2 分布式简介:添加时

同样,“kanagawa”“chiba”“saitama”“gunma”都是先选择服务器再保存。

接下来获取保存的数据。获取时也要将要获取的键“tokyo”传递给函数库。 函数库通过与数据保存时相同的算法,根据“键”选择服务器。 使用的算法相同,就能选中与保存时相同的服务器,然后发送get命令。 只要数据没有因为某些原因被删除,就能获得保存的值。

 

图3 分布式简介:获取时

这样,将不同的键保存到不同的服务器上,就实现了memcached的分布式。 memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障 
无法连接,也不会影响其他的缓存,系统依然能继续运行。

接下来介绍第1次 中提到的Perl客户端函数库Cache::Memcached实现的分布式方法。

 

3、   Cache::Memcached的分布式方法

Perl的memcached客户端函数库Cache::Memcached是 
memcached的作者Brad Fitzpatrick的作品,可以说是原装的函数库了。

该函数库实现了分布式功能,是memcached标准的分布式方法。

4、   根据余数计算分散

Cache::Memcached的分布式方法简单来说,就是“根据服务器台数的余数进行分散”。 
求得键的整数哈希值,再除以服务器台数,根据其余数来选择服务器。

下面将Cache::Memcached简化成以下的Perl脚本来进行说明。

use strict;
use warnings;
use String::CRC32;
 
my @nodes = ('node1','node2','node3');
my @keys = ('tokyo', 'kanagawa', 'chiba', 'saitama', 'gunma');
 
foreach my $key (@keys) {
    my $crc = crc32($key);             # CRC値
    my $mod = $crc % ( $#nodes + 1 );
    my $server = $nodes[ $mod ];       # 根据余数选择服务器
    printf "%s => %s\n", $key, $server;
}

Cache::Memcached在求哈希值时使用了CRC。

首先求得字符串的CRC值,根据该值除以服务器节点数目得到的余数决定服务器。 上面的代码执行后输入以下结果:

tokyo       => node2
kanagawa => node3
chiba       => node2
saitama   => node1
gunma     => node1

根据该结果,“tokyo”分散到node2,“kanagawa”分散到node3等。 多说一句,当选择的服务器无法连接时,Cache::Memcached会将连接次数 添加到键之后,再次计算哈希值并尝试连接。这个动作称为rehash。 不希望rehash时可以在生成Cache::Memcached对象时指定“rehash => 0”选项。

5、   根据余数计算分散的缺点

余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。 那就是当添加或移除服务器时,缓存重组的代价相当巨大。 添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器, 从而影响缓存的命中率。用Perl写段代码来验证其代价。

use strict;
use warnings;
use String::CRC32;
 
my @nodes = @ARGV;
my @keys = ('a'..'z');
my %nodes;
 
foreach my $key ( @keys ) {
    my $hash = crc32($key);
    my $mod = $hash % ( $#nodes + 1 );
    my $server = $nodes[ $mod ];
    push @{ $nodes{ $server } }, $key;
}
 
foreach my $node ( sort keys %nodes ) {
    printf "%s: %s\n", $node,  join ",", @{ $nodes{$node} };
}

这段Perl脚本演示了将“a”到“z”的键保存到memcached并访问的情况。 将其保存为mod.pl并执行。

首先,当服务器只有三台时:

$ mod.pl node1 node2 nod3
node1: a,c,d,e,h,j,n,u,w,x
node2: g,i,k,l,p,r,s,y
node3: b,f,m,o,q,t,v,z

结果如上,node1保存a、c、d、e……,node2保存g、i、k……, 每台服务器都保存了8个到10个数据。

接下来增加一台memcached服务器。

$ mod.pl node1 node2 node3 node4
node1: d,f,m,o,t,v
node2: b,i,k,p,r,y
node3: e,g,l,n,u,w
node4: a,c,h,j,q,s,x,z

 

添加了node4。可见,只有d、i、k、p、r、y命中了。像这样,添加节点后 键分散到的服务器会发生巨大变化。26个键中只有六个在访问原来的服务器, 其他的全都移到了其他服务器。命中率降低到23%。在Web应用程序中使用memcached时, 在添加memcached服务器的瞬间缓存效率会大幅度下降,负载会集中到数据库服务器上, 有可能会发生无法提供正常服务的情况。

mixi的Web应用程序运用中也有这个问题,导致无法添加memcached服务器。 
但由于使用了新的分布式方法,现在可以轻而易举地添加memcached服务器了。 这种分布式方法称为 Consistent Hashing。

 

 

五、Consistent Hashing

1、   Consistent Hashing的简单说明

Consistent Hashing如下所示:首先求出memcached服务器(节点)的哈希值, 
并将其配置到0~2SUP(32)的圆(continuum)上。 然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。 如果超过2SUP(32)仍然找不到服务器,就会保存到第一台memcached服务器上。

 

图4 Consistent Hashing:基本原理

从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化 而影响缓存的命中率,但Consistent Hashing中,只有在continuum上增加服务器的地点逆时针方向的 第一台服务器上的键会受到影响。

 

图5 Consistent Hashing:添加服务器

因此,Consistent Hashing最大限度地抑制了键的重新分布。 而且,有的Consistent Hashing的实现方法还采用了虚拟节点的思想。 使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。 因此,使用虚拟节点的思想,为每个物理节点(服务器) 在continuum上分配100~200个点。这样就能抑制分布不均匀, 最大限度地减小服务器增减时的缓存重新分布。

通过下文中介绍的使用Consistent Hashing算法的memcached客户端函数库进行测试的结果是, 由服务器台数(n)和增加的服务器台数(m)计算增加服务器后的命中率计算公式如下:

(1 - n/(n+m)) * 100

 

 

六、memcached的应用和兼容程序

1、   mixi案例研究

mixi在提供服务的初期阶段就使用了memcached。 
随着网站访问量的急剧增加,单纯为数据库添加slave已无法满足需要,因此引入了memcached。 此外,我们也从增加可扩展性的方面进行了验证,证明了memcached的速度和稳定性都能满足需要。 现在,memcached已成为mixi服务中非常重要的组成部分。

 

图1 现在的系统组件

2、   服务器配置和数量

mixi使用了许许多多服务器,如数据库服务器、应用服务器、图片服务器、 
反向代理服务器等。单单memcached就有将近200台服务器在运行。 memcached服务器的典型配置如下:

  • CPU:Intel Pentium 4 2.8GHz
  • 内存:4GB
  • 硬盘:146GB SCSI
  • 操作系统:Linux(x86_64)

这些服务器以前曾用于数据库服务器等。随着CPU性能提升、内存价格下降, 我们积极地将数据库服务器、应用服务器等换成了性能更强大、内存更多的服务器。 这样,可以抑制mixi整体使用的服务器数量的急剧增加,降低管理成本。 由于memcached服务器几乎不占用CPU,就将换下来的服务器用作memcached服务器了。

3、   memcached进程

每台memcached服务器仅启动一个memcached进程。分配给memcached的内存为3GB, 启动参数如下:

/usr/bin/memcached -p 11211 -u nobody -m 3000 -c 30720

由于使用了x86_64的操作系统,因此能分配2GB以上的内存。32位操作系统中, 每个进程最多只能使用2GB内存。也曾经考虑过启动多个分配2GB以下内存的进程, 但这样一台服务器上的TCP连接数就会成倍增加,管理上也变得复杂, 所以mixi就统一使用了64位操作系统。

另外,虽然服务器的内存为4GB,却仅分配了3GB,是因为内存分配量超过这个值, 就有可能导致内存交换(swap)。连载的第2次中 前坂讲解过了memcached的内存存储“slab allocator”,当时说过,memcached启动时 指定的内存分配量是memcached用于保存数据的量,没有包括“slab allocator”本身占用的内存、 以及为了保存数据而设置的管理空间。因此,memcached进程的实际内存分配量要比 指定的容量要大,这一点应当注意。

mixi保存在memcached中的数据大部分都比较小。这样,进程的大小要比 
指定的容量大很多。因此,我们反复改变内存分配量进行验证, 确认了3GB的大小不会引发swap,这就是现在应用的数值。

4、   memcached使用方法和客户端

现在,mixi的服务将200台左右的memcached服务器作为一个pool使用。 每台服务器的容量为3GB,那么全体就有了将近600GB的巨大的内存数据库。 客户端程序库使用了本连载中多次提到车的Cache::Memcached::Fast, 与服务器进行交互。当然,缓存的分布式算法使用的是 第4次介绍过的 Consistent Hashing算法。

应用层上memcached的使用方法由开发应用程序的工程师自行决定并实现。 但是,为了防止车轮再造、防止Cache::Memcached::Fast上的教训再次发生, 我们提供了Cache::Memcached::Fast的wrap模块并使用。

5、   通过Cache::Memcached::Fast维持连接

Cache::Memcached的情况下,与memcached的连接(文件句柄)保存在Cache::Memcached包内的类变量中。 
在mod_perl和FastCGI等环境下,包内的变量不会像CGI那样随时重新启动, 而是在进程中一直保持。其结果就是不会断开与memcached的连接, 减少了TCP连接建立时的开销,同时也能防止短时间内反复进行TCP连接、断开 而导致的TCP端口资源枯竭。

但是,Cache::Memcached::Fast没有这个功能,所以需要在模块之外 将Cache::Memcached::Fast对象保持在类变量中,以保证持久连接。

package Gihyo::Memcached;
 
use strict;
use warnings;
use Cache::Memcached::Fast;
 
my @server_list = qw/192.168.1.1:11211 192.168.1.1:11211/;
my $fast;  ## 用于保持对象
 
sub new {
    my $self  = bless {}, shift;
    if ( !$fast ) {
        $fast = Cache::Memcached::Fast->new({ servers => \@server_list });
    }
    $self->{_fast} = $fast;
    return $self;
}
 
sub get {
   my $self = shift;
   $self->{_fast}->get(@_);
}

上面的例子中,Cache::Memcached::Fast对象保存到类变量$fast中。

6、   公共数据的处理和rehash

诸如mixi的主页上的新闻这样的所有用户共享的缓存数据、设置信息等数据, 会占用许多页,访问次数也非常多。在这种条件下,访问很容易集中到某台memcached服务器上。 访问集中本身并不是问题,但是一旦访问集中的那台服务器发生故障导致memcached无法连接, 就会产生巨大的问题。

连载的第4次 中提到,Cache::Memcached拥有rehash功能,即在无法连接保存数据的服务器的情况下, 会再次计算hash值,连接其他的服务器。

但是,Cache::Memcached::Fast没有这个功能。不过,它能够在连接服务器失败时, 短时间内不再连接该服务器的功能。

my $fast = Cache::Memcached::Fast->new({
    max_failures     => 3,
    failure_timeout  => 1
});

在failuretimeout秒内发生maxfailures以上次连接失败,就不再连接该memcached服务器。 我们的设置是1秒钟3次以上。

此外,mixi还为所有用户共享的缓存数据的键名设置命名规则, 符合命名规则的数据会自动保存到多台memcached服务器中, 取得时从中仅选取一台服务器。创建该函数库后,就可以使memcached服务器故障 不再产生其他影响。

七、memcached应用经验

到此为止介绍了memcached内部构造和函数库,接下来介绍一些其他的应用经验。

1、   通过daemontools启动

通常情况下memcached运行得相当稳定,但mixi现在使用的最新版1.2.5 曾经发生过几次memcached进程死掉的情况。架构上保证了即使有几台memcached故障 也不会影响服务,不过对于memcached进程死掉的服务器,只要重新启动memcached, 就可以正常运行,所以采用了监视memcached进程并自动启动的方法。 于是使用了daemontools。

daemontools是qmail的作者DJB开发的UNIX服务管理工具集, 
其中名为supervise的程序可用于服务启动、停止的服务重启等。

这里不介绍daemontools的安装了。mixi使用了以下的run脚本来启动memcached。

#!/bin/sh
 
if [ -f /etc/sysconfig/memcached ];then
        . /etc/sysconfig/memcached
fi
 
exec 2>&1
exec /usr/bin/memcached -p $PORT -u $USER  -m $CACHESIZE -c $MAXCONN $OPTIONS

2、   监视

mixi使用了名为“nagios”的开源监视软件来监视memcached。

在nagios中可以简单地开发插件,可以详细地监视memcached的get、add等动作。 不过mixi仅通过stats命令来确认memcached的运行状态。

define command {
command_name                   check_memcached
command_line                   $USER1$/check_tcp -H $HOSTADDRESS$ -p 11211 -t 5 -E -s 'stats\r\nquit\r\n' -e 'uptime' -M crit
}

此外,mixi将stats目录的结果通过rrdtool转化成图形,进行性能监视, 并将每天的内存使用量做成报表,通过邮件与开发者共享。

3、   memcached的性能

连载中已介绍过,memcached的性能十分优秀。我们来看看mixi的实际案例。 这里介绍的图表是服务所使用的访问最为集中的memcached服务器。

 

图2 请求数

 

图3 流量

 

图4 TCP连接数

从上至下依次为请求数、流量和TCP连接数。请求数最大为15000qps, 流量达到400Mbps,这时的连接数已超过了10000个。 该服务器没有特别的硬件,就是开头介绍的普通的memcached服务器。 此时的CPU利用率为:

 

图5 CPU利用率

可见,仍然有idle的部分。因此,memcached的性能非常高, 可以作为Web应用程序开发者放心地保存临时数据或缓存数据的地方。

s

posted @ 2016-01-21 18:47  steven.木子  阅读(320)  评论(0编辑  收藏  举报