跳到主要内容

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_memoryRedis 分配的内存-
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-lfuLFU 淘汰(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 清除碎片

小结

本章我们学习了:

  1. 内存监控:INFO memory 命令和关键指标
  2. 内存配置:maxmemory 和淘汰策略
  3. 内存优化:数据结构选择、键名优化
  4. 碎片整理:主动整理和手动整理
  5. 大键处理:查找、删除和避免
  6. 监控告警:监控脚本和告警配置

练习

  1. 查看当前 Redis 实例的内存使用情况
  2. 配置 maxmemory 和淘汰策略
  3. 使用 redis-cli --bigkeys 扫描大键
  4. 编写内存监控脚本

参考资源