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大小增长 |
优化建议:
- 合理设置内存大小
- 调整增长因子减少浪费
- 启用Slab重新分配
- 监控内存使用情况
- 优化数据大小
下一步,让我们学习Memcached的过期策略!
最后更新:2026-03-27