12 - 内存管理
内存管理
12.1 内存使用分析
# 查看内存使用概况
redis-cli INFO memory
# 关键指标
redis-cli INFO memory | grep -E "used_memory_human|used_memory_peak_human|mem_fragmentation_ratio|mem_allocator"
# used_memory_human:2.50G
# used_memory_peak_human:3.20G
# mem_fragmentation_ratio:1.15
# mem_allocator:jemalloc-5.3.0
内存指标说明
| 指标 | 说明 | 正常范围 |
|---|---|---|
used_memory | Redis 分配器分配的内存总量 | — |
used_memory_rss | 操作系统看到的内存占用 | — |
mem_fragmentation_ratio | 内存碎片率(rss / used) | 1.0 - 1.5 |
used_memory_peak | 历史内存使用峰值 | — |
mem_allocator | 内存分配器 | jemalloc |
⚠️ 注意:碎片率 > 1.5 说明内存碎片严重;< 1.0 说明使用了 swap,性能会急剧下降。
单个 Key 内存分析
# 查看 Key 的内存占用
MEMORY USAGE user:1001
# (integer) 128
# 采样估算(大 Key 推荐)
MEMORY USAGE user:1001 SAMPLES 100
12.2 内存淘汰策略
当内存达到 maxmemory 限制时,Redis 根据配置的淘汰策略决定如何处理新写入。
八种淘汰策略
| 策略 | 范围 | 算法 | 说明 |
|---|---|---|---|
noeviction | — | — | 不淘汰任何 Key,新写入返回错误 |
allkeys-lru | 所有 Key | LRU | 淘汰最近最少使用的 Key |
allkeys-lfu | 所有 Key | LFU | 淘汰最不经常使用的 Key |
allkeys-random | 所有 Key | 随机 | 随机淘汰 Key |
volatile-lru | 有 TTL 的 Key | LRU | 淘汰有 TTL 的最近最少使用 Key |
volatile-lfu | 有 TTL 的 Key | LFU | 淘汰有 TTL 的最不经常使用 Key |
volatile-ttl | 有 TTL 的 Key | TTL | 淘汰 TTL 最小的 Key |
volatile-random | 有 TTL 的 Key | 随机 | 随机淘汰有 TTL 的 Key |
策略选择决策树
所有 Key 都是缓存吗?
├── 是 → allkeys-lru / allkeys-lfu
│ │
│ ├── 热点数据明显 → allkeys-lfu
│ └── 访问均匀 → allkeys-lru
│
└── 否(混合缓存和持久数据)
└── volatile-lru / volatile-lfu
│
├── 缓存有 TTL → volatile-lru
└── 需要更精确 → volatile-lfu
# 查看当前淘汰策略
redis-cli CONFIG GET maxmemory-policy
# 设置淘汰策略
redis-cli CONFIG SET maxmemory-policy allkeys-lru
# 设置最大内存
redis-cli CONFIG SET maxmemory 4gb
LRU vs LFU
| 特性 | LRU | LFU |
|---|---|---|
| 全称 | Least Recently Used | Least Frequently Used |
| 淘汰依据 | 最近最少访问 | 访问频率最低 |
| 计数器 | 访问时间戳 | 访问频率计数 |
| 适合场景 | 访问模式稳定 | 有明显热点数据 |
| 冷数据问题 | 不会(看时间) | 不会(看频率) |
LFU 算法使用对数计数器(logarithmic counter),并随时间衰减:
# 查看 Key 的 LFU 访问频率
OBJECT FREQ user:1001
# (integer) 15
# LFU 相关配置
redis-cli CONFIG SET lfu-log-factor 10 # 计数器增长速度
redis-cli CONFIG SET lfu-decay-time 1 # 衰减时间(分钟)
12.3 内存优化技巧
1. 使用 Hash 代替多个 String
# ❌ 低效:每个用户字段一个 Key
SET user:1001:name "张三"
SET user:1001:age "25"
SET user:1001:city "北京"
# ✅ 高效:使用 Hash 存储
HSET user:1001 name "张三" age "25" city "北京"
Hash 在元素较少时使用 listpack 编码,内存效率极高。
2. 使用整数集合(intset)
# Set 元素都是整数且数量少时,使用 intset 编码(极省内存)
SADD myset 1 2 3 4 5
OBJECT ENCODING myset # "intset"
3. 控制 Key 长度
# ❌ 长 Key
SET user:profile:information:1001:basic "data"
# ✅ 短 Key
SET u:1001 "data"
# 使用 Hash Tag 缩短长度
SET {u:1001}:n "张三"
4. 合理设置过期时间
# 不需要永久保存的数据都设置 TTL
SET session:abc "data" EX 3600
SET cache:hot "data" EX 300
5. 压缩 Value
# 使用压缩算法(如 gzip、snappy、lz4)压缩 Value
# Python 示例
import gzip, json, redis
r = redis.Redis()
data = json.dumps({"large": "data..."})
compressed = gzip.compress(data.encode())
r.set("key", compressed, ex=3600)
# 读取时解压
raw = r.get("key")
original = gzip.decompress(raw).decode()
6. 使用 UNLINK 代替 DEL
# DEL 大 Key 会阻塞
DEL big_key
# UNLINK 异步删除,不阻塞
UNLINK big_key
7. 整数共享池
Redis 内部维护了一个 0-9999 的整数对象池,使用这些整数时不会创建新对象:
# 这些整数共享同一个对象
SET counter 100
SET another 100
# 两个 Key 指向同一个 100 的 RedisObject
12.4 大 Key 检测
大 Key 是 Redis 性能杀手之一,可能导致慢查询、主从同步延迟、内存不均等问题。
大 Key 定义
| 类型 | 大 Key 标准 |
|---|---|
| String | Value > 10 KB |
| Hash/Set/ZSet | 元素数 > 5000 或 Value > 10 MB |
| List | 元素数 > 10000 或 Value > 10 MB |
检测方法
方法一:redis-cli –bigkeys
# 扫描所有 Key,找出每种类型中最大的 Key
redis-cli --bigkeys
# 输出示例:
# [00.00%] Biggest string found so far 'key:123' with 10485760 bytes
# [25.00%] Biggest hash found so far 'user:all' with 50000 fields
# [50.00%] Biggest set found so far 'tags:all' with 100000 members
# [75.00%] Biggest zset found so far 'leaderboard' with 50000 members
# [100.00%] Biggest list found so far 'queue:tasks' with 100000 items
# 慢速扫描(降低对生产环境的影响)
redis-cli --bigkeys --i 0.1 # 每次 SCAN 后休眠 0.1 秒
方法二:MEMORY USAGE 采样
# 对特定 Key 检查内存
MEMORY USAGE big_key SAMPLES 100
方法三:SCAN + MEMORY USAGE 脚本
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
BIG_KEY_THRESHOLD = 10240 # 10 KB
cursor = 0
big_keys = []
while True:
cursor, keys = r.scan(cursor, count=100)
for key in keys:
try:
mem = r.memory_usage(key, samples=100) or 0
if mem > BIG_KEY_THRESHOLD:
key_type = r.type(key)
big_keys.append({
'key': key,
'type': key_type,
'memory': mem,
})
print(f"Big Key: {key} | Type: {key_type} | Memory: {mem} bytes")
except Exception as e:
print(f"Error checking {key}: {e}")
if cursor == 0:
break
print(f"\nFound {len(big_keys)} big keys")
方法四:RDB 文件离线分析
# 使用 rdb-tools 离线分析 RDB 文件
pip install rdbtools python-lzf
# 生成 CSV 报告
rdb --command memory /var/lib/redis/dump.rdb --bytes 10240 --keys 100 > memory_report.csv
# 按内存排序
sort -t',' -k4 -nr memory_report.csv | head -20
大 Key 治理
# 1. 删除大 Key(使用 UNLINK 异步删除)
UNLINK big_key
# 2. 拆分大 Key
# 大 Hash → 多个小 Hash
HSCAN big_hash 0 COUNT 100
# 将字段分散到多个 Hash
# 3. 压缩 Value
# 对大 Value 进行压缩存储
# 4. 设置过期时间
EXPIRE big_key 3600
12.5 内存碎片整理
Redis 4.0+ 支持在线内存碎片整理:
# 开启碎片整理
redis-cli CONFIG SET activedefrag yes
# 碎片整理配置
redis-cli CONFIG SET active-defrag-ignore-bytes 104857600 # 碎片达到 100MB 才开始整理
redis-cli CONFIG SET active-defrag-threshold-lower 10 # 碎片率达到 10% 开始整理
redis-cli CONFIG SET active-defrag-threshold-upper 100 # 碎片率达到 100% 全力整理
redis-cli CONFIG SET active-defrag-cycle-min 1 # 整理占用 CPU 最小比例
redis-cli CONFIG SET active-defrag-cycle-max 25 # 整理占用 CPU 最大比例
📌 业务场景
场景一:缓存内存告警
# 监控内存使用率
redis-cli INFO memory | grep used_memory_human
# 接近 maxmemory 时触发告警
场景二:热点数据识别
# 使用 LFU 策略,淘汰冷数据
redis-cli CONFIG SET maxmemory-policy allkeys-lfu
场景三:大 Key 导致延迟
# 定期扫描大 Key
redis-cli --bigkeys --i 0.1
# 发现大 Key 后拆分或异步删除