06.1 - 内存优化策略
定位: Redis 数据在内存中,合理优化内存是生产环境的必修课 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: 淘汰策略选择、内存碎片成因、Big key 危害、编码优化
一、内存淘汰策略
当内存使用达到 maxmemory 时,Redis 触发淘汰策略:
| 策略 | 行为 |
|---|---|
| noeviction | 不淘汰 → 写入返回 OOM 错误 |
| allkeys-lru ★ | 从所有 key 中按 LRU 淘汰 |
| volatile-lru | 从设置了过期的 key 中淘汰 |
| allkeys-random | 随机淘汰 |
| volatile-ttl | 淘汰即将过期的 key |
| volatile-random | 从有过期的 key 中随机 |
| allkeys-lfu ★ | 从所有 key 中按 LFU 淘汰 |
| volatile-lfu | 从有过期的 key 中按 LFU 淘汰 |
推荐配置:allkeys-lru(大多数缓存场景);volatile-lfu(数据分级——重要 key 设过期,不重要 key 自然淘汰)
LRU vs LFU:
- LRU:最近最少使用(关注"多久没用了")
- LFU:最不经常使用(关注"用了多少次")
- 突发热点 → LRU 更合适(刚热起来不会被淘汰)
- 稳定访问 → LFU 更合适(低频率 key 被淘汰)
二、内存碎片
> **碎片产生原因**:
> 1. **内存分配器(jemalloc)特性**:固定大小分配(8/16/32/.../4KB),17 字节 → 分配 32 字节 → 15 字节碎片
> 2. **数据频繁增减**:SET A (1KB) → DEL A → SET B (2KB),释放的空间不连续 → 新数据放不下
> 3. **过期 key 删除**:大量 key 过期 → 释放碎片 → RF 升高
监控碎片率:
127.0.0.1:6379> INFO memory
used_memory_rss_human: 2.1G # 实际物理内存占用
used_memory_human: 1.5G # 实际数据占用
mem_fragmentation_ratio: 1.4 # 碎片率 = RSS/used
碎片率解读:
≈ 1.0:正常(RSS ≈ used)
< 1.0:发生了 swap(危险!内存用超了)
1.0-1.5:正常范围
> 1.5:碎片较多,需要清理
清理碎片(Redis 4.0+):
# 自动碎片清理
CONFIG SET activedefrag yes
CONFIG SET active-defrag-threshold-lower 10 # 碎片超过 10% 开始
CONFIG SET active-defrag-threshold-upper 100 # 超过 100% 全力清理
# 查看清理效果
INFO memory | grep defrag三、Big Key 问题
> **Big Key 的危害**:
> 1. **阻塞服务**:1 个 10MB 的 String → GET bigkey 耗时 10-50ms → 单线程事件循环被阻塞 → 所有请求排队等待
> 2. **内存不均**:集群中一个 key 占 1GB → 分布不均匀 → 一个节点内存快满,其他节点还有大量空间
> 3. **网络带宽**:GET 1MB key → 1MB 数据通过网络传输;1000 QPS → 1GB/s 网络占用
哪些是 Big Key?
String:> 10KB
List:元素超过 5000
Hash:元素超过 5000 或 value > 10KB
Set/ZSet:元素超过 5000
查找 Big Key:
redis-cli --bigkeys # 扫描统计大 key
redis-cli --memkeys # 按内存排序
DEBUG SDSLEN <key> # 查看具体 key 大小
MEMORY USAGE <key> # 显示 key 的内存使用
处理 Big Key:
✓ 拆分:hash = field1, field2, ... 替代大 String
✓ 压缩:客户端压缩后存储(Gzip/Zstd)
✓ 分批:对大 list/set 用 LRANGE/SCAN 分批处理
✓ 预警:设置最大内存限制 + 监控四、编码优化
不同数据类型的内存优化:
Hash 优化(推荐小对象用 ziplist):
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
→ 小数据用 ziplist(省 ~70% 内存)
→ 大数据 dict(高性能)
ZSet 优化:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
Set 优化:
set-max-intset-entries 512
→ 全整数时用 intset
内存占用对比:
数据类型 优化前 优化后
──────── ────── ──────
10万个小Hash dict: 150MB ziplist: 30MB ↓80%
100万整型Set hashtable: 70MB intset: 15MB ↓79%
100万 ZSet skiplist: 120MB ziplist: 40MB ↓67%五、实战验证
5.1 查看淘汰策略
bash
redis-cli
# 查看当前策略
127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
# 修改为 allkeys-lru
127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru
OK
# 查看内存限制
127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"
2) "4294967296" # 4GB5.2 查看内存碎片
bash
redis-cli
127.0.0.1:6379> INFO memory | grep -E "used_memory|fragmentation"
# used_memory: 1573741824 # 实际数据 1.5GB
# used_memory_rss: 2201235456 # RSS 2.1GB
# mem_fragmentation_ratio: 1.40 # 碎片率 1.4
# 启用碎片清理
127.0.0.1:6379> CONFIG SET activedefrag yes
OK5.3 查找大 Key
bash
# 扫描大 key(生产低峰期执行)
redis-cli --bigkeys
# 输出示例:
# Biggest string found so far '"bigkey:1"' with 5242880 bytes
# Biggest hash found so far '"users"' with 10234 fields
# Biggest set found so far '"tags"' with 50000 members
# 用 MEMORY USAGE 查看具体 key
redis-cli MEMORY USAGE "bigkey:1" # 字节
# 用 DEBUG 查看 string 大小
redis-cli STRLEN "bigkey:1"5.4 内存基准
bash
# 查看不同编码的内存差异
# 1000个 Hash 对象(每个 5 个字段)
# 小值(ziplist 编码)
for i in $(seq 1 1000); do
redis-cli HSET "small:hash:$i" name "user$i" age "$i" > /dev/null
done
redis-cli INFO memory | grep used_memory_human
# 大值(超过 64 字节)
for i in $(seq 1 1000); do
redis-cli HSET "big:hash:$i" name "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" age "$i" > /dev/null
done
redis-cli INFO memory | grep used_memory_human
# 观察内存差异(新创建 1000 个大值导致 hashtable 编码)六、面试视角
| 追问 | 答案要点 |
|---|---|
| 内存淘汰策略怎么选? | 缓存场景 allkeys-lru/LFU;数据库场景用 noeviction(避免丢数据) |
| mem_fragmentation_ratio < 1 意味着什么? | 内存发生 swap(危险!),说明物理内存不够,Redis 使用了 swap 分区 |
| 碎片清理(activedefrag)的原理? | Redis 4.0+ 自动整理碎片,通过搬移数据合并空闲块,主线程和后台线程协作 |
| Big key 怎么发现? | redis-cli --bigkeys 扫描;MONITOR 记录大响应;定期 scan + STRLEN/HLEN 抽样 |
| 1GB 的文件存 Redis 怎么存? | 不要存!Redis 不适合存大文件。拆分或换其他方案(对象存储 S3/MinIO) |
| 为什么 ziplist 能省内存? | 连续内存无指针开销、无哈希表浪费、编码紧凑(如小整数用 2 字节存) |
📚 相关链接
- **SDS** — SDS 的惰性释放和编码转换
- **Hash 编码** — ziplist→hashtable 的优化配置
- ← 返回 **性能优化与运维索引**