分析 Redis 内存使用:找出内存消耗大户
为什么要分析内存使用
Redis 基于内存运行,内存是最宝贵的资源。分析内存使用可以帮助你:
– 发现 BigKey,预防性能问题
– 评估是否需要扩容
– 优化数据结构,节省内存
– 排查内存泄漏
内存分析工具
1. INFO MEMORY 初步诊断
> INFO MEMORY
# Memory
used_memory:8589934592 # 8GB,Redis 实际使用
used_memory_rss:10737418240 # 10GB,系统分配(含碎片)
used_memory_peak:12884901888 # 12GB,历史峰值
used_memory_lua:37888
maxmemory:10737418240 # 10GB,限制
maxmemory_policy:allkeys-lru
mem_fragmentation_ratio:1.25 # 碎片率
mem_allocator:jemalloc-5.2.1
第一印象:
– 已用 8GB / 上限 10GB → 80%,偏高
– 碎片率 1.25 → 正常范围
– 峰值 12GB > 上限 10GB → 可能有内存抖动
2. MEMORY USAGE 单 key 分析
# 精确计算单个 key 的内存占用
MEMORY USAGE mykey
# (integer) 10240 -- 10KB
MEMORY USAGE mykey SAMPLES 10
# 对集合类型,可选抽样数加速计算
3. MEMORY STATS 详细统计
> MEMORY STATS
1) "peak.allocated" -- 历史峰值
2) (integer) 12884901888
3) "total.allocated" -- 当前分配
4) (integer) 8589934592
5) "startup.allocated" -- 启动开销
6) (integer) 831496
7) "replication.backlog" -- 复制积压
8) (integer) 8388608
9) "clients.slaves" -- 从客户端
10) (integer) 0
11) "clients.normal" -- 普通客户端
12) (integer) 0
13) "overhead.total" -- 总开销
14) (integer) 8388608
15) "keys.count" -- key 数量
16) (integer) 500000
17) "keys.bytes-per-key" -- 平均每 key 开销
18) (integer) 256
19) "dataset.bytes" -- 数据总大小
20) (integer) 8571547296
21) "dataset.percentage" -- 数据占比
22) "99.89%"
23) "peak.percentage" -- 峰值占比
24) "66.67%"
4. redis-cli –bigkeys 扫描
# 找出最大的 key
redis-cli --bigkeys
# 输出各种类型中最大的 key
5. 第三方内存分析工具
rdb tools(推荐):
# 使用 bgaof/rdb 文件离线分析
# 安装
pip install rdbtools
pip install redis-rdb-tools
# 分析 RDB 文件
rdb -c memory dump.rdb > memory.csv
# CSV 结果可按大小排序
Redis RDB Tools:
# 解析 RDB 文件生成内存报告
rdb -c memory /var/lib/redis/dump.rdb --bytes 1024 \
| sort -t',' -k4 -rn | head -20
内存开销详解
Redis 对象的内存开销
每个 key-value 在 Redis 中都有固定开销:
一个 String 类型的最小开销:
- dictEntry: ~32 bytes (key-value 对)
- key: 至少 1+N bytes
- redisObject: ~16 bytes
- value: 实际数据大小
- 对齐和指针: ~若干字节
总计:每个 key 至少 ~60-80 字节开销
示例计算:
# 1000 万个 key,每个 key 平均 20 字节,value 30 字节
fixed = 60 * 10_000_000 = 600MB # 固定开销
key_data = 20 * 10_000_000 = 200MB # key 本身
value_data = 30 * 10_000_000 = 300MB # value 本身
# 总计估算:1.1GB
不同数据结构的额外开销
| 数据结构 | 额外开销 | 说明 |
|---|---|---|
| String | 最小 | 基础开销 |
| Hash (ziplist) | 紧凑 | 小 hash 使用 ziplist,节省内存 |
| Hash (hashtable) | 每个 field 额外 32+ bytes | field 数 > 512 或 value > 64 时 |
| List (quicklist) | 链表节点开销 | 每个元素有前后指针 |
| Set | 同 hash | 内部用 hash 实现 |
| Sorted Set | zskiplist + hash | 双结构,内存开销更大 |
| Stream | rax tree | 消息树结构,开销较大 |
实际案例分析
案例一:大量短 key 导致内存膨胀
# ❌ 每个用户属性存为独立 key
r.set('user:1:name', 'Alice')
r.set('user:1:age', '30')
r.set('user:1:email', 'alice@example.com')
r.set('user:1:city', 'Beijing')
# 4 个 key × 60 字节开销 = 240 字节
# 实际数据只有 30 字节
# ✅ 合并到 Hash
r.hset('user:1', mapping={
'name': 'Alice',
'age': '30',
'email': 'alice@example.com',
'city': 'Beijing'
})
# 1 个 key + 4 个 field,总开销 ≈ 120 字节
# 节省 50%
案例二:使用更紧凑的编码
# ❌ 大量小整数用字符串存
for i in range(1000000):
r.set(f"id:{i}", str(i)) # 每个 key 约 60+ 字节开销
# ✅ 用位图存储 boolean/flag
r.setbit('online_users', user_id, 1) # 每位只占 1 bit
# 100 万用户在线状态:1000000/8 = 125KB
案例三:监控内存增长趋势
import redis
import time
r = redis.Redis()
while True:
info = r.info('memory')
used = info['used_memory'] / 1024 / 1024
rss = info['used_memory_rss'] / 1024 / 1024
ratio = info['mem_fragmentation_ratio']
print(f"Time={time.time():.0f}, "
f"Used={used:.0f}MB, "
f"RSS={rss:.0f}MB, "
f"Frag={ratio:.2f}")
time.sleep(60)
内存优化方案汇总
| 方案 | 节省潜力 | 实施难度 |
|---|---|---|
| 短 key 合并为 hash | 30-60% | 低 |
| 使用 ziplist/intset | 20-40% | 低 |
| 位图代替 set 标记 | > 90% | 中 |
| HyperLogLog 替代 SET 去重 | > 99% | 中 |
| 关闭未使用的特性 | 5-10% | 低 |
| 减少 key 前缀冗余 | 10-20% | 低 |
| 数据压缩(大 value) | 50-90% | 中 |
面试要点
- 内存分析工具链:
INFO MEMORY→MEMORY USAGE→redis-cli --bigkeys→ RDB 分析 - 固定开销是每个 key 都有的(~60 字节),key 越多开销越大
- Hash 比 String 省内存(字段共享 key 开销)
- Redis 内存不仅包括数据,还包括过期的 key(直到被清理)
- mem_fragmentation_ratio > 1.5 需要处理
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容