外观
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_chunks和free_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链表
- 回收流程:
- 检查LRU链表,找到最久未使用的Chunk
- 删除该Chunk中的数据
- 将Chunk标记为空闲
- 分配给新数据
内存回收监控
- 监控命令:
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回收情况,及时调整内存大小或优化缓存策略
