Skip to content

Memcached 内存分配机制

Slab Allocator 基本原理

  • 定义:Slab Allocator是Memcached的核心内存管理机制,用于高效分配和回收内存
  • 核心思想:将内存划分为固定大小的Slab,每个Slab包含多个固定大小的Chunk
  • 设计优势
    • 减少内存碎片
    • 提高内存分配效率
    • 简化内存管理
    • 支持快速回收

Slab Allocator 核心组件

  • Slab Class:不同大小Chunk的集合,每个Class有固定的Chunk大小
  • Page:内存页,大小默认为1MB,是Slab分配的基本单位
  • Slab:由多个Page组成,包含多个相同大小的Chunk
  • Chunk:实际存储数据的内存块,大小固定

内存分配流程

1. 内存初始化

  • 预分配内存:Memcached启动时,根据配置的内存大小预分配内存
  • Slab Class 创建:创建一系列Slab Class,每个Class对应不同的Chunk大小
  • Page 分配:为每个Slab Class分配初始的Page

2. Chunk 分配

  • 确定Slab Class:根据数据大小选择合适的Slab Class
  • 检查空闲Chunk:检查该Class是否有空闲Chunk
  • 分配Chunk:如果有空闲Chunk,直接分配;否则,申请新的Page
  • 新Page 分配:如果需要新Page,从预分配的内存中分配1MB Page,并分割为Chunk

3. 数据存储

  • 存储数据:将数据存储到分配的Chunk中
  • 更新元数据:记录Chunk的使用情况
  • 返回结果:返回存储结果给客户端

Slab Class 设计

Chunk 大小计算

  • 基础大小:默认最小Chunk大小为48字节
  • 增长因子:默认增长因子为1.25,控制Chunk大小的增长速率
  • 计算公式chunk_size = base_size * growth_factor^(class_id-1)
  • 示例
    • Class 1: 48字节
    • Class 2: 60字节 (48 * 1.25)
    • Class 3: 75字节 (60 * 1.25)
    • Class 4: 93字节 (75 * 1.25)

配置参数

  • -m, --memory:Memcached使用的总内存大小,默认64MB
  • -n, --slab-min-size:最小Chunk大小,默认48字节
  • -f, --slab-growth-factor:Chunk大小增长因子,默认1.25
  • -I, --max-item-size:最大Item大小,默认1MB

增长因子调整

  • 调整建议
    • 增长因子越小,内存利用率越高,但Slab Class数量越多
    • 增长因子越大,Slab Class数量越少,但内存浪费可能增加
    • 建议根据实际数据大小分布调整增长因子
  • 配置示例
    bash
    memcached -d -m 1024 -f 1.1 -n 64

内存碎片管理

碎片产生原因

  • 内部碎片:Chunk大小大于实际数据大小导致的浪费
  • 外部碎片:内存中存在大量小的空闲块,无法满足大内存分配需求

Slab Allocator 对碎片的处理

  • 减少内部碎片:通过调整增长因子,使Chunk大小更接近实际数据大小
  • 消除外部碎片:Slab Allocator分配的内存是连续的,不会产生外部碎片
  • 内存复用:释放的Chunk可以被重新分配,提高内存利用率

碎片监控

  • 监控命令stats slabs
  • 关键指标
    • active_slabs:活跃的Slab数量
    • total_malloced:分配的总内存
    • 每个Slab Class的used_chunksfree_chunks
  • 示例输出
    STAT 1:chunk_size 48
    STAT 1:chunks_per_page 21845
    STAT 1:total_pages 1
    STAT 1:total_chunks 21845
    STAT 1:used_chunks 100
    STAT 1:free_chunks 21745
    STAT 1:free_chunks_end 0

内存回收机制

惰性回收

  • Memcached 不主动回收内存:Memcached不会主动回收过期数据占用的内存
  • 惰性删除:当访问过期数据时,才会检查并删除,释放Chunk
  • 优势:减少了后台回收线程的CPU开销
  • 劣势:过期数据可能长时间占用内存

内存耗尽处理

  • LRU 策略:当内存耗尽时,使用LRU(Least Recently Used)策略回收最久未使用的Chunk
  • LRU 实现:每个Slab Class维护一个LRU链表
  • 回收流程
    1. 检查LRU链表,找到最久未使用的Chunk
    2. 删除该Chunk中的数据
    3. 将Chunk标记为空闲
    4. 分配给新数据

内存回收监控

  • 监控命令stats items
  • 关键指标
    • number:Item数量
    • age:最久未访问Item的时间
    • evicted:LRU回收的Item数量
    • evicted_nonzero:非过期被回收的Item数量
    • outofmemory:内存不足的次数
  • 示例输出
    STAT items:1:number 100
    STAT items:1:age 3600
    STAT items:1:evicted 0
    STAT items:1:evicted_nonzero 0
    STAT items:1:outofmemory 0

内存优化策略

1. 调整增长因子

  • 根据数据大小分布调整:如果数据大小分布较集中,可调整增长因子更接近1
  • 命令示例
    bash
    memcached -d -m 1024 -f 1.1
  • 效果:减少内部碎片,提高内存利用率

2. 调整最小Chunk大小

  • 根据实际数据大小调整:如果大多数数据大于默认的48字节,可增大最小Chunk大小
  • 命令示例
    bash
    memcached -d -m 1024 -n 64
  • 效果:减少小Chunk的数量,提高大数据的存储效率

3. 合理设置内存大小

  • 根据业务需求调整:避免内存过大导致资源浪费,或过小导致频繁LRU回收
  • 监控内存使用率:保持内存使用率在合理范围内,避免频繁LRU
  • 命令示例
    bash
    memcached -d -m 2048

4. 优化数据大小

  • 压缩数据:对大型数据进行压缩,减少内存占用
  • 拆分数据:将大型数据拆分为多个小数据,提高内存利用率
  • 优化数据结构:使用更紧凑的数据结构,减少数据大小

5. 启用Slab页预分配

  • 配置参数-o slab_automove=1
  • 功能:自动将空闲页从低使用率的Slab Class移动到高使用率的Class
  • 效果:提高内存利用率,减少LRU回收
  • 命令示例
    bash
    memcached -d -m 1024 -o slab_automove=1

常见问题(FAQ)

Q1: 为什么Memcached内存使用率总是100%?

A1: Memcached的设计是预分配所有配置的内存,所以内存使用率通常显示为100%。实际的内存使用情况可以通过stats slabs命令查看各个Slab Class的Chunk使用情况。

Q2: 如何减少Memcached的内存浪费?

A2: 减少Memcached内存浪费的方法包括:

  • 调整增长因子,使Chunk大小更接近实际数据大小
  • 调整最小Chunk大小,适合实际数据大小
  • 优化数据大小,压缩或拆分大型数据
  • 启用Slab页预分配,自动调整Slab Class的内存分配

Q3: 如何选择合适的增长因子?

A3: 选择合适的增长因子需要考虑:

  • 数据大小分布:如果数据大小分布集中,使用较小的增长因子
  • 内存利用率要求:要求高内存利用率,使用较小的增长因子
  • CPU开销:较小的增长因子会增加Slab Class数量,可能增加CPU开销
  • 建议进行测试,找到适合业务场景的增长因子

Q4: Memcached会产生内存碎片吗?

A4: Memcached使用Slab Allocator机制,主要产生内部碎片,而不会产生外部碎片。内部碎片可以通过调整增长因子和最小Chunk大小来减少。

Q5: 如何监控Memcached的内存使用情况?

A5: 监控Memcached内存使用情况可以使用以下命令:

  • stats:查看总体内存使用情况
  • stats slabs:查看各个Slab Class的内存使用情况
  • stats items:查看各个Slab Class的Item和LRU情况
  • 使用监控工具,如Prometheus、Zabbix等

Q6: 当Memcached内存不足时会发生什么?

A6: 当Memcached内存不足时,会使用LRU策略回收最久未使用的Chunk。具体表现为:

  • evicted计数器增加
  • 可能出现outofmemory错误
  • 缓存命中率可能下降
  • 建议监控LRU回收情况,及时调整内存大小或优化缓存策略