Memcached内存管理 #

一、内存管理概述 #

1.1 为什么需要内存管理 #

text
传统内存管理问题:

1. 内存碎片
   - 频繁分配释放导致碎片
   - 无法分配大块内存

2. 性能问题
   - malloc/free开销大
   - 内存整理耗时

3. 管理复杂
   - 需要跟踪每个内存块
   - 容易内存泄漏

1.2 Memcached内存管理方案 #

text
Memcached使用Slab Allocator:

1. 预分配内存
   - 启动时分配所有内存
   - 避免运行时分配

2. 按大小分组
   - 不同大小的数据使用不同的Slab
   - 减少内存碎片

3. 简单高效
   - 分配释放都是O(1)
   - 无需复杂的内存管理

二、Slab Allocator原理 #

2.1 基本概念 #

text
Slab Allocator三个层次:

1. Slab Class(Slab类)
   - 按Chunk大小分类
   - 每个Slab Class管理一种大小的Chunk

2. Slab(内存页)
   - 默认1MB大小
   - 包含多个Chunk

3. Chunk(内存块)
   - 实际存储数据的最小单位
   - 大小固定

结构图:
┌─────────────────────────────────────────┐
│            Slab Class 1 (96B)           │
├─────────────────────────────────────────┤
│  Slab 1                                 │
│  ┌──────┬──────┬──────┬──────┬──────┐  │
│  │Chunk │Chunk │Chunk │Chunk │Chunk │  │
│  │ 96B  │ 96B  │ 96B  │ 96B  │ 96B  │  │
│  └──────┴──────┴──────┴──────┴──────┘  │
├─────────────────────────────────────────┤
│  Slab 2                                 │
│  ┌──────┬──────┬──────┬──────┬──────┐  │
│  │Chunk │Chunk │Chunk │Chunk │Chunk │  │
│  │ 96B  │ 96B  │ 96B  │ 96B  │ 96B  │  │
│  └──────┴──────┴──────┴──────┴──────┘  │
└─────────────────────────────────────────┘

2.2 Slab Class #

text
Slab Class按Chunk大小分类:

默认配置:
Class 1:  96 bytes
Class 2:  120 bytes
Class 3:  152 bytes
Class 4:  192 bytes
Class 5:  240 bytes
Class 6:  304 bytes
Class 7:  384 bytes
Class 8:  480 bytes
...

增长因子(Growth Factor):
- 默认1.25
- 每个Class的大小是前一个的1.25倍

计算公式:
chunk_size[n] = chunk_size[n-1] * growth_factor

2.3 内存分配过程 #

text
内存分配流程:

1. 启动时预分配
   memcached -m 64
   → 分配64MB内存

2. 创建Slab Class
   → 根据增长因子创建不同大小的Class

3. 数据存储
   → 根据数据大小选择合适的Slab Class
   → 分配一个Chunk存储数据

示例:
存储100字节的数据
→ 选择Class 2(120字节)
→ 分配一个120字节的Chunk
→ 实际使用100字节,浪费20字节

三、查看Slab信息 #

3.1 stats slabs命令 #

bash
# 查看Slab统计
stats slabs
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 1
STAT 1:total_chunks 10922
STAT 1:used_chunks 100
STAT 1:free_chunks 10822
STAT 1:free_chunks_end 0
STAT 1:get_hits 5000
STAT 1:cmd_set 2000
STAT 1:delete_hits 50
STAT 1:incr_hits 10
STAT 1:decr_hits 5
STAT 1:cas_hits 3
STAT 1:cas_badval 2
STAT 1:touch_hits 20
STAT active_slabs 1
STAT total_malloced 1048576
END

3.2 字段说明 #

text
stats slabs字段说明:

chunk_size      - Chunk大小(字节)
chunks_per_page - 每个Slab的Chunk数量
total_pages     - Slab数量
total_chunks    - 总Chunk数量
used_chunks     - 已使用的Chunk数量
free_chunks     - 空闲的Chunk数量
free_chunks_end - Slab末尾的空闲Chunk数量
get_hits        - 该Slab的命中次数
cmd_set         - 该Slab的set命令次数
delete_hits     - 该Slab的删除次数
active_slabs    - 活跃的Slab数量
total_malloced  - 已分配的总内存(字节)

3.3 stats items命令 #

bash
# 查看Item统计
stats items
STAT items:1:number 100
STAT items:1:age 3600
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 0
STAT items:1:expired_unfetched 0
STAT items:1:evicted_unfetched 0
END

3.4 使用memcached-tool #

bash
# 查看Slab详细信息
memcached-tool 127.0.0.1:11211 display

# 输出示例
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  1     96B      3600s       1     100     no        0        0    0
  2     120B     3600s       1      50     no        0        0    0
  3     152B     3600s       1      30     no        0        0    0

# 字段说明
Item_Size - Chunk大小
Max_age   - 最老数据的年龄
Pages     - Slab页数
Count     - 存储的数据数量
Full?     - Slab是否已满
Evicted   - 被淘汰的数据数量
OOM       - 内存不足次数

四、内存分配策略 #

4.1 选择Slab Class #

text
数据存储时选择Slab Class:

1. 计算数据大小
   data_size = key_length + value_length + flags + ...

2. 选择最小的能容纳数据的Slab Class
   找到 chunk_size >= data_size 的最小Class

3. 分配Chunk
   如果有空闲Chunk,直接使用
   如果没有,创建新的Slab或淘汰旧数据

示例:
数据大小:100字节
可选Slab Class:96B, 120B, 152B...
选择:120B(Class 2)
浪费:20字节

4.2 内存浪费 #

text
内存浪费原因:

1. Chunk大小固定
   - 数据大小不匹配
   - 部分空间浪费

2. Slab对齐
   - Slab默认1MB
   - 可能无法完全利用

示例:
存储50字节数据
选择96字节的Chunk
浪费:46字节(48%)

优化方法:
- 调整增长因子
- 优化数据大小

4.3 Slab重新分配 #

text
Slab重新分配(Slab Reassignment):

1. 启用重新分配
   memcached -o slab_reassign

2. 手动重新分配
   slab_reassign <source_class> <dest_class>

3. 自动平衡
   memcached -o slab_automove=1

示例:
# 将Slab从Class 1移动到Class 2
slab_reassign 1 2

# 查看状态
stats slabs

五、内存优化 #

5.1 调整增长因子 #

bash
# 默认增长因子1.25
memcached -f 1.25

# 更小的增长因子(减少浪费)
memcached -f 1.1

# 更大的增长因子(减少Slab数量)
memcached -f 1.5

# 示例
# 增长因子1.1
Class 1:  96B
Class 2:  106B
Class 3:  117B
Class 4:  129B
...

# 增长因子1.5
Class 1:  96B
Class 2:  144B
Class 3:  216B
Class 4:  324B
...

5.2 调整最小Chunk大小 #

bash
# 默认最小Chunk大小
memcached -n 48

# 调整最小Chunk大小
memcached -n 80

# 最小Chunk大小 + 增长因子决定所有Chunk大小

5.3 调整Slab页大小 #

bash
# 默认Slab页大小1MB
memcached -L 1m

# 调整Slab页大小
memcached -L 2m

# 更大的Slab页可以减少页数量
# 但可能增加内存碎片

5.4 最大Item大小 #

bash
# 默认最大Item大小1MB
memcached -I 1m

# 调整最大Item大小
memcached -I 2m

# 注意:需要重新编译才能支持更大的Item

六、内存监控 #

6.1 查看内存使用 #

bash
# 查看统计信息
stats
STAT bytes 1000000              # 当前存储的字节数
STAT limit_maxbytes 67108864    # 最大内存限制
STAT maxbytes 67108864          # 最大内存限制
END

# 计算内存使用率
使用率 = bytes / limit_maxbytes

6.2 监控脚本 #

bash
#!/bin/bash
# monitor_memory.sh

HOST="127.0.0.1"
PORT="11211"

# 获取统计信息
stats=$(echo "stats" | nc $HOST $PORT)

# 解析数据
bytes=$(echo "$stats" | grep "STAT bytes " | awk '{print $3}')
maxbytes=$(echo "$stats" | grep "STAT limit_maxbytes" | awk '{print $3}')

# 计算使用率
usage=$(echo "scale=2; $bytes * 100 / $maxbytes" | bc)

# 获取Slab信息
slabs=$(echo "stats slabs" | nc $HOST $PORT)
active_slabs=$(echo "$slabs" | grep "STAT active_slabs" | awk '{print $3}')
total_malloced=$(echo "$slabs" | grep "STAT total_malloced" | awk '{print $3}')

# 输出结果
echo "Memcached内存监控"
echo "=================="
echo "内存使用率: ${usage}%"
echo "已使用字节: $bytes"
echo "最大字节数: $maxbytes"
echo "活跃Slab数: $active_slabs"
echo "已分配内存: $total_malloced"

6.3 持续监控 #

bash
# 每秒监控内存使用
watch -n 1 'echo "stats" | nc 127.0.0.1 11211 | grep -E "bytes|limit_maxbytes"'

# 每分钟监控
watch -n 60 'memcached-tool 127.0.0.1:11211'

七、内存问题排查 #

7.1 内存不足 #

bash
# 查看Evicted数量
stats items
STAT items:1:evicted 100    # 被淘汰的数据数量

# 如果Evicted持续增长,说明内存不足

# 解决方案:
# 1. 增加内存
memcached -m 256

# 2. 减少数据量
# 3. 缩短过期时间

7.2 内存碎片 #

bash
# 查看Slab使用情况
memcached-tool 127.0.0.1:11211

# 如果某些Slab使用率低,其他Slab使用率高
# 说明存在内存碎片

# 解决方案:
# 1. 调整增长因子
memcached -f 1.15

# 2. 启用Slab重新分配
memcached -o slab_reassign,slab_automove=1

7.3 内存浪费 #

bash
# 查看数据大小分布
stats sizes
STAT 96 50
STAT 120 30
STAT 152 20

# 如果大部分数据集中在某些大小
# 可以调整增长因子减少浪费

# 示例:大部分数据在100字节左右
# 调整增长因子使Chunk大小更接近

八、内存优化实践 #

8.1 数据大小优化 #

python
# 优化数据大小,减少内存浪费

# 不好的做法
data = {
    "id": 1001,
    "name": "John Doe",
    "email": "john@example.com",
    "description": "A very long description..."  # 可能很长
}

# 好的做法
data = {
    "id": 1001,
    "name": "John Doe",
    "email": "john@example.com"
}
# 长文本单独存储或压缩

8.2 键名优化 #

python
# 优化键名长度

# 不好的做法
key = "user_information_cache:user_id_1001"

# 好的做法
key = "u:1001"  # 简短但有意义

# 键名也会占用内存

8.3 压缩数据 #

python
import json
import gzip
import base64

def compress_data(data):
    # 序列化
    json_str = json.dumps(data)
    
    # 压缩
    compressed = gzip.compress(json_str.encode())
    
    # Base64编码
    encoded = base64.b64encode(compressed).decode()
    
    return encoded

def decompress_data(encoded):
    # Base64解码
    compressed = base64.b64decode(encoded.encode())
    
    # 解压
    json_str = gzip.decompress(compressed).decode()
    
    # 反序列化
    data = json.loads(json_str)
    
    return data

# 使用
data = {"id": 1001, "name": "John", "description": "..." * 1000}
compressed = compress_data(data)
client.set("user:1001", compressed, flags=3)  # flags=3表示压缩

九、内存配置最佳实践 #

9.1 开发环境 #

bash
# 开发环境配置
memcached -d -m 64 -p 11211 -u root

# 说明:
# -m 64     : 64MB内存,足够开发测试
# -p 11211  : 默认端口
# -u root   : 以root用户运行

9.2 生产环境 #

bash
# 生产环境配置
memcached -d -m 2048 -p 11211 -u memcache -c 4096 -t 8 -f 1.15 -o slab_reassign,slab_automove=1

# 说明:
# -m 2048   : 2GB内存
# -c 4096   : 最大4096个连接
# -t 8      : 8个工作线程
# -f 1.15   : 增长因子1.15
# -o ...    : 启用Slab重新分配和自动平衡

9.3 大数据环境 #

bash
# 大数据环境配置
memcached -d -m 8192 -p 11211 -u memcache -c 8192 -t 16 -I 2m -f 1.1 -o slab_reassign,slab_automove=2

# 说明:
# -m 8192   : 8GB内存
# -c 8192   : 最大8192个连接
# -t 16     : 16个工作线程
# -I 2m     : 最大Item 2MB
# -f 1.1    : 增长因子1.1(减少浪费)
# -o ...    : 启用Slab重新分配和自动平衡

十、总结 #

内存管理要点:

概念 说明
Slab Class 按Chunk大小分类
Slab 内存页,默认1MB
Chunk 最小内存块
增长因子 控制Chunk大小增长

优化建议:

  1. 合理设置内存大小
  2. 调整增长因子减少浪费
  3. 启用Slab重新分配
  4. 监控内存使用情况
  5. 优化数据大小

下一步,让我们学习Memcached的过期策略!

最后更新:2026-03-27