Redis 内存淘汰策略
Redis 把数据放在内存里,内存总有上限。当写入使内存超过配置的 maxmemory 上限时,Redis 必须决定"腾出空间给新数据",这就是内存淘汰(Eviction)策略。做缓存时选错策略会导致命中率低下甚至写失败,是上线前必须想清楚的配置。
配置内存上限
maxmemory 为 0 表示不限制(默认),设置了上限后内存触顶才触发淘汰。可在 redis.conf 配置,也可运行时用 CONFIG SET 动态调整、无需重启:
# redis.conf 中配置
maxmemory 100mb
maxmemory-policy allkeys-lru
maxmemory-samples 5 # 淘汰采样数,默认 5,见下文
CONFIG SET maxmemory 100mb
# 输出:OK
CONFIG SET maxmemory-policy allkeys-lru
# 输出:OK
CONFIG GET maxmemory
# 输出:1) "maxmemory"
# 2) "104857600"
八种策略
| 策略 | 淘汰范围 | 语义 |
|---|---|---|
| noeviction | 不淘汰 | 内存满后写命令直接报错(默认策略) |
| allkeys-lru | 所有键 | 淘汰最近最少使用的键 |
| allkeys-lfu | 所有键 | 淘汰使用频率最低的键(需 4.0+) |
| allkeys-random | 所有键 | 随机淘汰 |
| volatile-lru | 设了 TTL 的键 | 只在会过期的键里按 LRU 淘汰 |
| volatile-lfu | 设了 TTL 的键 | 只在会过期的键里按 LFU 淘汰 |
| volatile-random | 设了 TTL 的键 | 只在会过期的键里随机淘汰 |
| volatile-ttl | 设了 TTL 的键 | 优先淘汰剩余存活时间最短的键 |
volatile-* 系列在"没有任何设了 TTL 的键"时退化为 noeviction 行为(不淘汰、写报错)。
LRU 与 LFU 的近似实现
真正的 LRU 需记录每个键的最后访问时间,内存代价大。Redis 采用采样近似:从键空间随机抽 maxmemory-samples(默认 5)个键,淘汰其中"最久未用"的一个,效果接近真实 LRU。LFU 则为每个键维护一个随时间衰减的访问计数器(可用 lfu-log-factor、lfu-decay-time 调节增长与衰减),能识别"访问次数少但最近被碰过"的冷门键,适合热点集中的场景。
淘汰与过期的区别
过期(Expire)是"键自带 TTL、到点删除",属于数据自身生命周期;淘汰是"内存触顶、按策略挑键删除",属于资源不够时的被动腾挪。两者相互独立:不过期的键可能被 allkeys-* 淘汰;设了 TTL 的键即使没到点,也可能被策略提前淘汰。
noeviction 下的表现
把策略设为 noeviction 且内存写满时,写命令会直接报 OOM 错误,读命令不受影响:
CONFIG SET maxmemory 5mb
# 输出:OK
SET bigkey "aaaaaaaaaaaaaaaaaaaaaaaa…(写入超过 5MB 的字符串)"
# 输出:(error) OOM command not allowed when used memory > 'maxmemory'.
被淘汰的键数量可在 INFO stats 的 evicted_keys 字段观察:
INFO stats
# 输出:… evicted_keys:0 …(每淘汰一个键该计数 +1)
实战选型建议
- 纯缓存、数据可重建:allkeys-lru 最省心;热点极集中(如热门榜单)可换 allkeys-lfu。
- 只有一部分键允许淘汰(设了 TTL),其余必须常驻:选 volatile-lru。
- 数据不可丢(计数、会话强一致):绝不能开淘汰,保持 noeviction 并做好容量监控与告警。
- volatile-random、volatile-ttl 少见;volatile-ttl 会优先删"本来就要过期"的键,缓存意义不大,一般不用。
小结
内存淘汰 = maxmemory 上限 + 策略选择:缓存场景首选 allkeys-lru / allkeys-lfu,不可丢数据用 noeviction 并监控容量。它和"过期"是两回事——过期是键的寿命到点,淘汰是内存不够时的被动删除。淘汰发生在写路径上、一旦触发有性能开销,容量规划比策略调优更重要。