分析 Redis 内存使用:找出内存消耗大户

分析 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 MEMORYMEMORY USAGEredis-cli --bigkeys → RDB 分析
  • 固定开销是每个 key 都有的(~60 字节),key 越多开销越大
  • Hash 比 String 省内存(字段共享 key 开销)
  • Redis 内存不仅包括数据,还包括过期的 key(直到被清理)
  • mem_fragmentation_ratio > 1.5 需要处理
© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容