第2章 理解 Memcached 的内存存储

优质
小牛编辑
131浏览
2023-12-01

上一章的文章介绍了 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 class1: chunk size128 perslab8192
slab class2: chunk size256 perslab4096
slab class3: chunk size512 perslab2048
slab class4: chunk size1024 perslab1024
slab class5: chunk size2048 perslab512
slab class6: chunk size4096 perslab256
slab class7: chunk size8192 perslab128
slab class8: chunk size16384 perslab64
slab class9: chunk size32768 perslab32
slab class10: chunk size65536 perslab16
slab class11: chunk size 131072 perslab8
slab class12: chunk size 262144 perslab4
slab class13: chunk size 524288 perslab2

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

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

slab class1: chunk size88 perslab11915
slab class2: chunk size112 perslab9362
slab class3: chunk size144 perslab7281
slab class4: chunk size184 perslab5698
slab class5: chunk size232 perslab4519
slab class6: chunk size296 perslab3542
slab class7: chunk size376 perslab2788
slab class8: chunk size472 perslab2221
slab class9: chunk size592 perslab1771
slab class10: chunk size744 perslab1409

可见组间差距比因子为 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_SizeMax_age1MB_pages CountFull?
1104 B1394292 s1215 12249628yes
2136 B1456795 s52400919yes
3176 B1339587 s33196567yes
4224 B1360926 s109510221yes
5280 B1570071 s49183452yes
6352 B1592051 s77229197yes
7440 B1517732 s66157183yes
8552 B1460821 s62117697yes
9696 B1521917 s143215308yes
10872 B1695035 s205246162yes
111.1 kB 1681650 s233221968yes
121.3 kB 1603363 s241183621yes
131.7 kB 1634218 s9457197yes
142.1 kB 1695038 s7536488yes
152.6 kB 1747075 s6525203yes
163.3 kB 1760661 s7824167yes

各列的含义为:

  • Item_Size:Chunk大小
  • Max_age:LRU内最旧的记录的生存时间
  • 1MB_pages:分配给 Slab 的页数 Count Slab 内的记录数
  • Full?:Slab内是否含有空闲 chunk

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

2.7 总结

本次简单说明了Memcached 的缓存机制和调优方法。希望读者能理解 Memcached 的内存管理原理及其优缺点。

下次将继续说明 LRU 和 Expire 等原理,以及 Memcached 的最新发展方向——可扩充体系(pluggable architecher)。