Memcached 的内存模型是其高效性能的核心之一。它使用了一种基于 slab 的内存分配机制,将内存划分为多个固定大小的块(slab),并通过 LRU(最近最少使用)算法管理内存。
Slab 分配机制
Memcached 使用 slab 分配器来管理内存,这是一种高效的内存分配方式,避免了频繁的内存碎片问题。
Slab 的基本概念
Slab:Memcached 将内存划分为多个 slab,每个 slab 是一个固定大小(1MB)的内存块;
Chunk:每个 slab 被进一步划分为多个相同大小的 chunk,chunk 是存储键值对的最小单位;
Slab Class:Memcached 中有多个 slab class,每个 slab class 对应一种 chunk 大小。chunk 大小从最小(如 80 字节)逐渐递增(通过增长因子控制)。
Slab 的工作流程
内存初始化
Memcached 启动时,将可用内存划分为多个 slab class,每个 slab class 包含多个 slab;
每个 slab 被划分为多个相同大小的 chunk。
数据存储
当存储一个键值对时,Memcached 会根据数据大小选择合适的 slab class;
从对应的 slab 中分配一个 chunk 来存储数据。
内存回收
当数据过期或被删除时,chunk 会被标记为空闲,供后续使用。
Slab 的动态分配
按需分配
Memcached 不会在启动时预先分配所有 slab,而是根据需要动态分配 slab;
当某个 slab class 的 chunk 用完时,Memcached 会从空闲内存中分配一个新的 slab 给该 slab class。
Slab 重新分配
如果某个 slab class 的内存使用率较低,而另一个 slab class 的内存不足,Memcached 可以通过
slab reassign
机制将一个 slab 从一个 slab class 转移到另一个 slab class。
Slab Class 和 Chunk 大小
Chunk 大小增长
Memcached 通过增长因子(
-f
参数,默认 1.25)控制 chunk 大小的增长;例如,如果最小 chunk 大小为 80 字节,增长因子为 1.25,则后续 slab class 的 chunk 大小分别为 100 字节、125 字节等。
选择合适的 Slab Class
存储数据时,Memcached 会选择能够容纳数据的最小 chunk;
如果数据大小超过最大 chunk 大小,Memcached 会拒绝存储(除非启用
-I
参数调整最大 item 大小)。
LRU 算法
Memcached 使用 LRU(最近最少使用)算法管理内存。当内存不足时,Memcached 会优先淘汰最近最少使用的数据。
LRU 队列
每个 slab class 维护一个 LRU 队列,记录 chunk 的使用情况;
当需要分配新的 chunk 时,Memcached 会从 LRU 队列的尾部淘汰数据。
惰性删除
Memcached 不会立即删除过期数据,而是在访问时检查数据的过期时间;
如果数据已过期,则将其标记为可回收。
内存碎片
由于 Memcached 使用固定大小的 chunk,可能会导致内存碎片问题。为了减少内存碎片,可以通过调整增长因子(-f
参数)来优化 chunk 大小的分布。
内部碎片:如果数据大小小于 chunk 大小,剩余的空间会被浪费;
外部碎片:不同 slab class 之间的内存分配可能导致外部碎片。
内存管理参数
Memcached 提供了一些参数来调整内存模型的行为:
-m
:设置最大内存使用量(单位为 MB);-f
:设置增长因子,控制 chunk 大小的增长;-n
:设置每个 chunk 的最小空间(用于存储键和元数据);-I
:设置最大 item 大小(默认 1MB,最大可调整为 128MB)。
内存模型示例
假设 Memcached 的配置如下:
最大内存:64MB;
最小 chunk 大小:80 字节;
增长因子:1.25。
则可能的内存分配如下:
Slab Class 1:chunk 大小 = 80 字节;
Slab Class 2:chunk 大小 = 100 字节;
Slab Class 3:chunk 大小 = 125 字节;
...
当存储一个 90 字节的键值对时,Memcached 会选择 Slab Class 2(100 字节),并分配一个 chunk 来存储数据。