Redis作为内存数据库,其内存管理机制直接影响系统性能和稳定性。当内存使用达到maxmemory限制时,Redis会触发内存淘汰机制,这是每个Redis使用者必须掌握的核心知识。
Redis提供了7种内存淘汰策略,每种策略都有其适用场景和实现原理:
| 策略名称 | 作用范围 | 淘汰依据 | 适用场景 | 版本支持 |
|---|---|---|---|---|
| noeviction | 不淘汰 | 直接拒绝写入 | 数据不可丢失场景 | 所有版本 |
| allkeys-lru | 所有key | 最近最少使用 | 通用缓存场景 | 2.8+ |
| allkeys-lfu | 所有key | 最不经常使用 | 热点数据稳定场景 | 4.0+ |
| volatile-lru | 带TTL的key | 最近最少使用 | 带过期时间的缓存 | 2.8+ |
| volatile-lfu | 带TTL的key | 最不经常使用 | 带TTL的热点数据 | 4.0+ |
| volatile-ttl | 带TTL的key | 剩余生存时间 | 临时数据场景 | 2.8+ |
| volatile-random | 带TTL的key | 随机淘汰 | 特殊测试场景 | 2.8+ |
| allkeys-random | 所有key | 随机淘汰 | 极少使用 | 2.8+ |
提示:生产环境最常用的是allkeys-lru和volatile-lru策略,它们能较好地平衡内存使用和缓存命中率。
Redis的LRU实现并非传统双向链表,而是采用近似LRU算法:
这种实现方式内存开销小(O(1)),虽然不够精确但实际效果良好。可通过maxmemory-samples参数调整采样数量。
bash复制# 配置LRU采样数量
config set maxmemory-samples 10
Redis 4.0引入的LFU算法在LRU基础上增加了访问频率统计:
LFU特别适合存在稳定热点数据的场景,如电商商品详情缓存。
bash复制# 查看key的LFU计数
object freq key_name
注意事项:更改淘汰策略会导致原有缓存行为变化,应在低峰期操作并监控命中率。
大Key是Redis运维中最常见的问题之一,可能导致服务雪崩。完整的大Key治理方案应包括发现、拆分、删除全流程。
| 数据类型 | 大Key阈值 | 风险等级 |
|---|---|---|
| String | >10KB | 中 |
| Hash/Set | >5,000元素 | 高 |
| List/ZSet | >10,000元素 | 极高 |
| 问题类型 | 影响范围 | 持续时间 | 恢复难度 |
|---|---|---|---|
| 网络阻塞 | 客户端 | 操作期间 | 易 |
| 服务阻塞 | 所有请求 | 操作期间 | 难 |
| 内存不均 | 集群节点 | 持续 | 中 |
| AOF重写 | 主线程 | 持久化期间 | 难 |
bash复制# 扫描大Key(生产慎用)
redis-cli --bigkeys
# 抽样扫描(更安全)
redis-cli --bigkeys -i 0.1 # 每100ms扫描100次
bash复制# 精确测量key内存
MEMORY USAGE key_name
# 内存统计
INFO memory
python复制def scan_big_keys(redis_conn, threshold=10240):
cursor = 0
big_keys = []
while True:
cursor, keys = redis_conn.scan(cursor, count=100)
for key in keys:
size = redis_conn.memory_usage(key)
if size > threshold:
big_keys.append((key, size))
if cursor == 0:
break
return big_keys
原始大JSON:
json复制{
"user": {
"id": 123,
"profile": {...},
"orders": [...],
"addresses": [...]
}
}
拆分方案:
code复制user:123:profile
user:123:orders:page1
user:123:addresses
原始大Hash:
code复制user_session:123
|- field1: value1
|- field2: value2
...
|- field10000: value10000
分片存储:
code复制user_session:123:shard1
user_session:123:shard2
...
user_session:123:shard10
分片算法:
java复制// 获取分片key
String getShardKey(String baseKey, String field, int shardCount) {
int shard = Math.abs(field.hashCode()) % shardCount;
return baseKey + ":shard" + shard;
}
| 操作 | 执行方式 | 阻塞时间 | 内存回收 | 适用场景 |
|---|---|---|---|---|
| DEL | 同步 | O(N) | 立即 | 小Key |
| UNLINK | 异步 | O(1) | 延迟 | 大Key |
对于特大Key(如百万元素Set):
python复制def delete_large_hash(redis_conn, key, batch_size=100):
cursor = 0
while True:
cursor, fields = redis_conn.hscan(key, cursor, count=batch_size)
if not fields:
break
redis_conn.hdel(key, *fields.keys())
time.sleep(0.01) # 控制删除速度
数据建模规范:
监控体系:
bash复制# 监控大Key增长
redis-cli --latency-history -i 10
# 慢查询监控
config set slowlog-log-slower-than 10000
架构设计:
问题现象:
解决方案:
code复制cart:user123:electronics
cart:user123:clothing
效果:
问题现象:
优化方案:
java复制// 分级存储方案
public void followUser(long userId, long followerId) {
// 活跃粉丝存Redis
if (isActiveUser(followerId)) {
redis.sadd("active:followers:"+userId, followerId);
}
// 全量存数据库
sql.execute("INSERT INTO follows VALUES(?,?)", userId, followerId);
}
bash复制# 内存淘汰策略
maxmemory-policy allkeys-lru
# LRU采样精度
maxmemory-samples 5
# 最大内存限制
maxmemory 16gb
# 懒惰释放
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
replica-lazy-flush yes
bash复制# 内存使用情况
redis-cli info memory
# 淘汰key统计
redis-cli info stats | grep evicted_keys
# 大Key监控
redis-cli --bigkeys -i 0.1
在实际业务中处理Redis大Key问题时,我发现最有效的办法是在设计阶段就预防大Key产生。比如最近我们重构了一个用户画像系统,将原来单个用户10KB的JSON数据拆分为多个Hash结构,并采用分片存储方案。改造后不仅解决了大Key问题,还使读取性能提升了5倍。关键是要建立完善的数据规范,并在代码审查中加入Redis使用检查项。