第2章 理解 Memcached 的内存存储
上一章的文章介绍了 Memcached 是分布式的高速缓存服务器。本次将介绍 Memcached 的内部构造的实现方式,以及内存的管理方式。另外 Memcached 的内部构造导致的弱点也将加以说明。
2.1 Slab Allocation 机制:整理内存以便重复使用
最近的 Memcached 默认情况下采用了名为 Slab Allocator 的机制分配、管理内存。在该机制出现以前,内存的分配是通过对所有记录简单地进行 malloc 和 free 来进行的。但是这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下,会导致操作系统比 Memcached 进程本身还慢。Slab Allocator 就是为解决该问题而诞生的。
下面来看看 Slab Allocator 的原理。下面是 Memcached 文档中的 Slab Allocator 的目标:
the primary goal of the slabs subsystem in memcached was to eliminate memory fragmentation issues totally by using fixedsize memory chunks coming from a few predetermined size classes.
也就是说,Slab Allocator 的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,以完全解决内存碎片问题。
Slab Allocation 的原理相当简单。将分配的内存分割成各种尺寸的块(chunk),并把尺寸相同的块分成组(chunk的集合)(图 2.1)。
图 2.1:Slab Allocation 的构造图而且,Slab Allocator 还有重复使用已分配的内存的目的。也就是说,分配到的内存不会释放,而是重复利用。
Slab Allocation的主要术语
- Page:分配给 Slab的内存空间,默认是1MB。分配给 Slab之后根据slab的大小切分成chunk。
- Chunk:用于缓存记录的内存空间。
- Slab Class:特定大小的chunk的组。
2.2 在 Slab 中缓存记录的原理
下面说明 Memcached 如何针对客户端发送的数据选择 slab 并缓存到 chunk 中。
Memcached 根据收到的数据的大小,选择最适合数据大小的 slab(图 2.2)。Memcached 中保存着 slab 内空闲 chunk 的列表,根据该列表选择 chunk,然后将数据缓存于其中。
图 2.2:选择存储记录的组的方法
实际上,Slab Allocator 也是有利也有弊。下面介绍一下它的缺点。
2.3 Slab Allocator 的缺点
Slab Allocator 解决了当初的内存碎片问题,但新的机制也给 Memcached 带来了新的问题。
这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。例如,将 100 字节的数据缓存到 128 字节的 chunk 中,剩余的 28 字节就浪费了(图 2.3)。
图 2.3:chunk 空间的使用
对于该问题目前还没有完美的解决方案,但在文档中记载了比较有效的解决方案。
The most efficient way to reduce the waste is to use a list of size classes that closely matches (if that's at all possible) common sizes of objects that the clients of this particular installation of memcached are likely to store.
就是说,如果预先知道客户端发送的数据的公用大小,或者仅缓存大小相同的数据的情况下,只要使用适合数据大小的组的列表,就可以减少浪费。
但是很遗憾,现在还不能进行任何调优,只能期待以后的版本了。但是我们可以调节 slab class 的大小的差别。接下来说明 growth factor 选项。
2.4 使用 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 的利用率等各种各样的信息。
2.5 查看 Memcached 的内部状态
Memcached 有个名为 stats 的命令,使用它可以获得各种各样的信息。执行命令的方法很多,用 telnet 最为简单:
$ telnet 主机名 端口号
连接到 Memcached 之后,输入 stats 再按回车,即可获得包括资源利用率在内的各种信息。此外输入 stats slabs 或 stats items 还可以获得关于缓存记录的信息。结束程序请输入 quit。
这些命令的详细信息可以参考 Memcached 软件包内的 protocol.txt 文档。
$ telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
stats
STAT pid 481
STAT uptime 16574
STAT time 1213687612
STAT version 1.2.5
STAT pointer_size 32
STAT rusage_user 0.102297
STAT rusage_system 0.214317
STAT curr_items 0
STAT total_items 0
STAT bytes 0
STAT curr_connections 6
STAT total_connections 8
STAT connection_structures 7
STAT cmd_get 0
STAT cmd_set 0
STAT get_hits 0
STAT get_misses 0
STAT evictions 0
STAT bytes_read 20
STAT bytes_written 465
STAT limit_maxbytes 67108864
STAT threads 4
END
quit
另外如果安装了 libmemcached 这个面向 C/C++ 语言的客户端库,就会安装 memstat 这个命令。使用方法很简单,可以用更少的步骤获得与 telnet 相同的信息,还能一次性从多台服务器获得信息。
$ memstat servers=server1,server2,server3,...
libmemcached 可以从下面的地址获得:
http://tangent.org/552/libmemcached.html
2.6 查看 slabs 的使用状况
使用 Memcached 的创造着 Brad 写的名为 memcachedtool 的 Perl 脚本,可以方便地获得 slab 的使用情况(它将 Memcached 的返回值整理成容易阅读的格式)。可以从下面的地址获得脚本:
http://code.sixapart.com/svn/memcached/trunk/server/scripts/memcachedtool
使用方法也极其简单:
$ memcachedtool 主机名:端口 选项
查看 slabs 使用状况时无需指定选项,因此用下面的命令即可:
$ memcachedtool 主机名:端口
获得的信息如下所示:
# | 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 |
各列的含义为:
- Item_Size:Chunk大小
- Max_age:LRU内最旧的记录的生存时间
- 1MB_pages:分配给 Slab 的页数 Count Slab 内的记录数
- Full?:Slab内是否含有空闲 chunk
从这个脚本获得的信息对于调优非常方便,强烈推荐使用。
2.7 总结
本次简单说明了Memcached 的缓存机制和调优方法。希望读者能理解 Memcached 的内存管理原理及其优缺点。
下次将继续说明 LRU 和 Expire 等原理,以及 Memcached 的最新发展方向——可扩充体系(pluggable architecher)。