Redis 内存管理
Redis 是内存数据库,有效的内存管理对性能和稳定性至关重要。本章介绍 Redis 内存管理机制和优化策略。
内存使用概览
查看内存信息
# 查看内存使用情况
127.0.0.1:6379> INFO memory
# 输出示例
# Memory
used_memory:1000000 # 已使用内存(字节)
used_memory_human:976.56K # 人类可读格式
used_memory_rss:2000000 # 操作系统分配的内存
used_memory_peak:1500000 # 历史最大内存使用
used_memory_peak_human:1.43M
used_memory_lua:37888 # Lua 引擎内存
used_memory_scripts:0 # Lua 脚本缓存
used_memory_scripts_human:0B
maxmemory:0 # 最大内存限制(0 表示无限制)
maxmemory_human:0B
maxmemory_policy:noeviction # 内存淘汰策略
mem_fragmentation_ratio:2.00 # 内存碎片率
关键指标说明
| 指标 | 说明 | 理想值 |
|---|---|---|
used_memory | Redis 分配的内存 | - |
used_memory_rss | 操作系统分配的内存 | 接近 used_memory |
mem_fragmentation_ratio | 碎片率 (rss/used) | 1.0 - 1.5 |
maxmemory | 最大内存限制 | 根据服务器配置 |
maxmemory_policy | 淘汰策略 | 根据场景选择 |
内存碎片
┌─────────────────────────────────────────────────────────────┐
│ 内存碎片示意 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Redis 分配的内存块: │
│ ┌───────┬───────┬───────┬───────┬───────┐ │
│ │ 数据 │ 空闲 │ 数据 │ 空闲 │ 数据 │ │
│ └───────┴───────┴───────┴───────┴───────┘ │
│ │
│ 碎片率 = used_memory_rss / used_memory │
│ │
│ 碎片率 < 1.0:Redis 使用了 swap,性能下降 │
│ 碎片率 > 1.5:内存碎片较多,需要整理 │
│ 碎片率 ≈ 1.0-1.5:正常 │
│ │
└─────────────────────────────────────────────────────────────┘
内存配置
设置最大内存
# 配置文件
maxmemory 4gb
# 命令设置(运行时)
127.0.0.1:6379> CONFIG SET maxmemory 4gb
OK
内存淘汰策略
当内存达到 maxmemory 限制时,Redis 根据配置的策略删除键:
| 策略 | 说明 | 适用场景 |
|---|---|---|
noeviction | 不淘汰,返回错误 | 数据不能丢失 |
allkeys-lru | 所有键 LRU 淘汰 | 缓存场景 |
volatile-lru | 有 TTL 的键 LRU 淘汰 | 缓存+持久数据 |
allkeys-random | 随机淘汰所有键 | 无访问热点 |
volatile-random | 随机淘汰有 TTL 的键 | 无访问热点 |
volatile-ttl | 淘汰即将过期的键 | 数据有时效性 |
allkeys-lfu | LFU 淘汰(Redis 4.0+) | 有访问频率差异 |
volatile-lfu | 有 TTL 的键 LFU 淘汰 | 有访问频率差异 |
# 配置文件
maxmemory-policy allkeys-lru
# 运行时设置
127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru
OK
LRU vs LFU
┌─────────────────────────────────────────────────────────────┐
│ LRU vs LFU │
├─────────────────────────────────────────────────────────────┤
│ │
│ LRU (Least Recently Used) 最近最少使用 │
│ ├── 淘汰最长时间未被访问的键 │
│ └── 适合:访问模式稳定,有访问热点 │
│ │
│ LFU (Least Frequently Used) 最不经常使用 │
│ ├── 淘汰访问频率最低的键 │
│ └── 适合:访问频率差异大,防止冷数据占用内存 │
│ │
│ 示例: │
│ 键 A:昨天访问 100 次,今天未访问 │
│ 键 B:过去一周每天访问 1 次 │
│ │
│ LRU:淘汰 A(最近未访问) │
│ LFU:淘汰 B(访问频率低) │
│ │
└─────────────────────────────────────────────────────────────┘
淘汰策略配置
# redis.conf
# 淘汰策略
maxmemory-policy allkeys-lru
# LRU/LFU 采样数量(越大越精确,但越慢)
maxmemory-samples 5
# LFU 衰减时间(分钟)
lfu-decay-time 1
# LFU 计数器对数因子
lfu-log-factor 10
内存优化
数据结构优化
1. 使用合适的数据类型
# 存储用户信息
# 方式一:多个 String 键(不推荐)
SET user:1:name "张三"
SET user:1:age "25"
SET user:1:email "[email protected]"
# 内存开销大,每个键都有元数据
# 方式二:Hash 结构(推荐)
HSET user:1 name "张三" age 25 email "[email protected]"
# 内存开销小,一个键存储多个字段
2. 使用压缩列表
当 Hash、List、ZSet 元素较少时,Redis 使用压缩列表(ziplist)节省内存:
# redis.conf
# Hash 压缩配置
hash-max-listpack-entries 512 # 最大元素数
hash-max-listpack-value 64 # 最大值长度(字节)
# List 压缩配置
list-max-listpack-size -2 # 负数表示字节限制
# ZSet 压缩配置
zset-max-listpack-entries 128
zset-max-listpack-value 64
# Set 压缩配置
set-max-intset-entries 512 # 整数集合最大元素数
3. 使用更紧凑的编码
# 小整数使用 int 编码
SET counter 100
# 对象编码:int(节省内存)
# 大整数或浮点数使用 embstr/raw 编码
SET large_number 12345678901234567890
# 对象编码:raw
# 查看编码
127.0.0.1:6379> OBJECT ENCODING counter
"int"
键名优化
# 键名不要太长
user:1001:profile # 好
user_information_for_user_id_1001_profile # 不好
# 使用简短但有意义的键名
u:1001:p # 太短,不直观
user:1001:profile # 平衡
过期策略优化
# 设置合理的过期时间
SET cache:data "value" EX 3600 # 1小时过期
# 避免大量键同时过期
# 不好的做法:所有缓存设置相同过期时间
for i in range(10000):
redis.set(f"cache:{i}", data, ex=3600)
# 好的做法:添加随机偏移
import random
for i in range(10000):
redis.set(f"cache:{i}", data, ex=3600 + random.randint(0, 300))
内存碎片整理
检查碎片
127.0.0.1:6379> INFO memory | grep fragmentation
mem_fragmentation_ratio:1.50
主动碎片整理
Redis 4.0+ 支持主动碎片整理:
# redis.conf
# 启用主动碎片整理
activedefrag yes
# 碎片整理触发条件
active-defrag-ignore-bytes 100mb # 忽略小于 100MB 的碎片
active-defrag-threshold-lower 10 # 碎片率超过 10% 开始整理
active-defrag-threshold-upper 100 # 碎片率超过 100% 强制整理
# 整理速度限制
active-defrag-cycle-min 1 # 最小 CPU 使用率
active-defrag-cycle-max 25 # 最大 CPU 使用率
手动触发整理
# Redis 4.0+
127.0.0.1:6379> MEMORY PURGE
OK
大键问题
什么是大键?
大键是指包含大量数据或占用大量内存的键:
# 大键示例
- String 类型:值超过 1MB
- Hash 类型:包含数万个字段
- List 类型:包含数百万个元素
- Set 类型:包含数百万个成员
- ZSet 类型:包含数百万个成员
查找大键
# 使用 redis-cli 扫描
redis-cli --bigkeys
# 输出示例
-------- summary -------
Sampled 100000 keys in the keyspace!
Total key length in bytes is 2000000 (avg len 20.00)
Biggest string found 'big:key:1' has 10485760 bytes
Biggest list found 'big:list' has 1000000 items
Biggest hash found 'big:hash' has 50000 fields
Biggest zset found 'big:zset' has 100000 members
# 使用 MEMORY USAGE 命令
127.0.0.1:6379> MEMORY USAGE mykey
(integer) 102400
删除大键
# 不要直接 DEL 大键,会阻塞 Redis
# 方式一:异步删除(Redis 4.0+)
127.0.0.1:6379> UNLINK bigkey
(integer) 1
# 方式二:分批删除(Hash)
127.0.0.1:6379> HSCAN big:hash 0
# 获取字段后分批 HDEL
# 方式三:分批删除(List)
127.0.0.1:6379> LTRIM big:list 0 -1001
# 每次删除部分元素
# 方式四:分批删除(Set)
127.0.0.1:6379> SSCAN big:set 0
# 获取成员后分批 SREM
避免大键
# 拆分大键
# 不好的做法:一个 Hash 存储所有用户
HSET users user:1 "张三" user:2 "李四" ... user:100000 "王五"
# 好的做法:按 ID 范围分片
HSET users:1 user:1 "张三" user:2 "李四" ... user:1000 "赵六"
HSET users:2 user:1001 "..." ...
内存监控
监控脚本
import redis
import time
def monitor_memory(r, interval=60):
"""监控 Redis 内存使用"""
while True:
info = r.info('memory')
used_memory = info['used_memory'] / 1024 / 1024 # MB
used_memory_rss = info['used_memory_rss'] / 1024 / 1024
frag_ratio = info['mem_fragmentation_ratio']
maxmemory = info.get('maxmemory', 0) / 1024 / 1024
print(f"已用内存: {used_memory:.2f} MB")
print(f"RSS 内存: {used_memory_rss:.2f} MB")
print(f"碎片率: {frag_ratio:.2f}")
if maxmemory > 0:
usage_percent = used_memory / maxmemory * 100
print(f"内存使用率: {usage_percent:.1f}%")
print("-" * 40)
time.sleep(interval)
r = redis.Redis()
monitor_memory(r)
告警配置
def check_memory_alert(r, warning_threshold=0.8, critical_threshold=0.9):
"""内存告警检查"""
info = r.info('memory')
maxmemory = info.get('maxmemory', 0)
if maxmemory == 0:
print("警告:未设置 maxmemory")
return
usage = info['used_memory'] / maxmemory
if usage >= critical_threshold:
print(f"严重:内存使用率 {usage*100:.1f}% 超过临界阈值 {critical_threshold*100}%")
elif usage >= warning_threshold:
print(f"警告:内存使用率 {usage*100:.1f}% 超过警告阈值 {warning_threshold*100}%")
else:
print(f"正常:内存使用率 {usage*100:.1f}%")
内存调优建议
1. 合理设置 maxmemory
# 留出内存给操作系统和其他进程
# 建议:maxmemory = 物理内存 * 60% - 80%
# 例如:16GB 服务器
maxmemory 10gb
2. 选择合适的淘汰策略
| 场景 | 推荐策略 |
|---|---|
| 纯缓存 | allkeys-lru |
| 缓存+持久数据 | volatile-lru |
| 数据有时效性 | volatile-ttl |
| 访问频率差异大 | allkeys-lfu |
| 数据不能丢失 | noeviction |
3. 配置过期策略
# 定期删除频率(Hz)
# 值越大,过期键清理越及时,但 CPU 消耗越高
hz 10
# 动态调整 Hz(Redis 6.0+)
# 根据过期键数量自动调整
dynamic-hz yes
4. 减少内存碎片
# 启用主动碎片整理
activedefrag yes
# 或者重启 Redis 清除碎片
小结
本章我们学习了:
- 内存监控:INFO memory 命令和关键指标
- 内存配置:maxmemory 和淘汰策略
- 内存优化:数据结构选择、键名优化
- 碎片整理:主动整理和手动整理
- 大键处理:查找、删除和避免
- 监控告警:监控脚本和告警配置
练习
- 查看当前 Redis 实例的内存使用情况
- 配置 maxmemory 和淘汰策略
- 使用 redis-cli --bigkeys 扫描大键
- 编写内存监控脚本