Redis 的可用内存过高时的自动驱逐 key 策略详解原创
# Redis 的可用内存过高时的自动驱逐 key 策略详解
# 引言
Redis 是一个高性能的 key-value 存储,广泛应用于缓存、会话存储等场景。在使用 Redis 时,经常会遇到内存使用过高的问题。为了保证 Redis 的稳定运行,我们需要了解 Redis 如何在内存接近上限时自动驱逐 key 以释放内存。
# Redis 的内存管理机制
Redis 通过配置参数 maxmemory 来设置允许使用的最大内存量。当 Redis 的内存使用量接近 maxmemory 时,它会根据配置的驱逐策略自动删除部分 key 以释放内存。我们可以通过配置参数 maxmemory-policy 来指定驱逐策略。
# 驱逐策略详解
Redis 提供了多种驱逐策略,每种策略适用于不同的应用场景。以下是 Redis 的几种主要驱逐策略:
# 1. noeviction
当内存达到最大限制时,不再执行任何写操作,直接返回错误。适用于希望数据完全保留且可以容忍写入失败的场景。
# 2. allkeys-lru
在所有 key 中,使用 LRU(Least Recently Used)算法驱逐最近最少使用的 key。这是最常用的策略,适用于缓存场景。
# 3. volatile-lru
仅在设置了过期时间的 key 中,使用 LRU 算法驱逐最近最少使用的 key。适用于只希望驱逐设置了过期时间的 key 的场景。
# 4. allkeys-random
在所有 key 中,随机驱逐一些 key。适用于不希望使用 LRU 算法,且可以接受随机驱逐的场景。
# 5. volatile-random
仅在设置了过期时间的 key 中,随机驱逐一些 key。适用于只希望驱逐设置了过期时间的 key 且可以接受随机驱逐的场景。
# 6. volatile-ttl
仅在设置了过期时间的 key 中,优先驱逐剩余生存时间(TTL)最短的 key。适用于希望优先删除即将过期的 key 的场景。
# 配置示例
可以通过修改 Redis 配置文件或运行时使用 CONFIG SET 命令来配置 maxmemory 和 maxmemory-policy。以下是一个配置示例:
# 设置最大内存为 256MB
maxmemory 256mb
# 设置驱逐策略为 allkeys-lru
maxmemory-p<PASSWORD> allkeys-lru
2
3
4
5
或者在运行时设置:
CONFIG SET maxmemory 256mb
CONFIG SET maxmemory-p<PASSWORD> allkeys-lru
2
# 监控与调优
为了更好地管理 Redis 的内存使用,可以结合 Redis 提供的监控工具,如 INFO memory 命令,监控内存使用情况。同时,可以根据实际应用需求和内存使用情况,动态调整 maxmemory 和驱逐策略。
# 为什么 LRU 是近似的
Redis 的 allkeys-lru 和 volatile-lru 采用近似 LRU 算法,而非严格 LRU:
严格 LRU 的问题:
- 需要为每个 key 维护访问时间戳,并在淘汰时遍历所有 key 找出最久未使用的
- 时间复杂度 O(N),对单线程 Redis 来说成本太高
近似 LRU 的实现(Redis 3.0+):
- 维护一个候选池(默认 5 个 key)
- 每次需要淘汰时,随机采样 N 个 key(
maxmemory-samples,默认 5),放入候选池 - 淘汰候选池中最久未使用的 key
与严格 LRU 的差异:
- 采样数量越大,近似度越高,但 CPU 开销也越大
- 默认 5 个样本,Redis 官方测试显示近似度接近 90%
- 如果业务对 LRU 准确性要求极高,可以调大
maxmemory-samples(如 10)
配置建议:
CONFIG SET maxmemory-samples 10 # 提高近似 LRU 精度,增加一点 CPU 开销
# 验证策略生效
# 1. 确认当前策略
redis-cli CONFIG GET maxmemory-p<PASSWORD>
redis-cli CONFIG GET maxmemory
2
# 2. 估算合理的 maxmemory
# 查看当前内存使用
redis-cli INFO memory | grep used_memory_human
# 查看 key 数量
redis-cli DBSIZE
# 估算每个 key 平均大小(字节)
# 粗略公式:used_memory / keyspace_hits(或 DBSIZE)
2
3
4
5
6
7
8
设置原则:
- 单机部署:
maxmemory设为物理内存的 60%-70%,留出 fork 重写时的缓冲 - 容器部署:设为容器 limit 的 80% 左右
# 3. 触发驱逐观察
# 临时把 maxmemory 设低,强制触发驱逐
redis-cli CONFIG SET maxmemory 10485760 # 10MB
# 写入大量数据触发驱逐
redis-cli debug populate 100000 test 1000
# 观察驱逐统计
redis-cli INFO stats | grep evicted_keys
2
3
4
5
6
7
8
看到 evicted_keys 增加,说明驱逐策略正在工作。
# 4. 确认淘汰的是冷数据
# 观察命中率变化
redis-cli INFO stats | grep keyspace_hit_ratio
2
如果驱逐后命中率大幅下降,说明可能误删了热 key,需要调整策略或增加内存。
# 坑与边界
allkeys-lru 误删热 key:刚被访问的 key 如果在采样时没被抽中,仍可能被误判为「冷」而删除。对「绝对不能丢」的热数据,应设置过期时间并改用
volatile-lru,或直接不设maxmemory(用noeviction)。volatile- 在无过期时间 key 上的回退行为*:如果所有 key 都没设过期时间,
volatile-lru、volatile-random、volatile-ttl的效果等同于noeviction(拒绝写入)。这可能让应用以为 Redis 挂了。驱逐与持久化的并发问题:AOF 重写(
BGREWRITEAOF)或 RDB 快照(BGSAVE)需要fork()子进程,内存会瞬间翻倍。如果maxmemory设置过满,fork可能失败或触发 OOM。建议maxmemory至少预留 30% 缓冲。LFU 的计数器溢出:3.0+ 新增
allkeys-lfu/volatile-lfu,用访问频率而非时间判断。但计数器会随时间衰减,极端场景下高频 key 也可能被误判。大 key 的驱逐开销:一个 100MB 的 key 被淘汰时,删除操作会阻塞主线程。如果大量使用
allkeys-random,可能随机到大 key 导致延迟尖刺。noeviction 不是免费的:当内存打满后,每次写入都会返回错误,应用层需要处理异常。如果未妥善处理,可能导致业务逻辑中断而非简单的缓存失效。