内存满了,写入失败
Redis 的所有数据住在内存里,而内存不是无限的。当用量撞上 maxmemory 的上限,Redis 面临选择:新写入怎么办?默认答案是 noeviction——拒绝写入,直接报错。对纯缓存场景这不是好答案:缓存本来就该有「旧的让位给新的」的自我代谢能力,这个代谢机制就是淘汰策略。
先把概念划清楚:上一篇讲的过期删除是 key 到了自己的 TTL,理应离开;淘汰是内存压力下的驱逐,TTL 还没到也可能被清。前者是合同到期,后者是提前裁员。
八种策略,两个维度
淘汰策略的命名规则就藏着它的结构:allkeys 系在全部 key 里挑淘汰对象,volatile 系只挑设了 TTL 的 key;挑的标准有 LRU、LFU、random、ttl 四种,再加上 noeviction,一共八种:
noeviction 默认:内存满,写入报错
allkeys-lru 全部key里,淘汰最近最少使用的(最常用)
allkeys-lfu 全部key里,淘汰访问频率最低的(4.0+)
allkeys-random 全部key里,随机淘汰
volatile-lru 仅设了TTL的key里,LRU淘汰
volatile-lfu 仅设了TTL的key里,LFU淘汰
volatile-random 仅设了TTL的key里,随机淘汰
volatile-ttl 仅设了TTL的key里,优先淘汰剩余寿命最短的LRU 和 LFU 的差别值得咀嚼:LRU 看时间——最近被摸过的留,哪怕它一天只被访问一次;LFU 看频率——累计访问频率低的走,哪怕它刚被访问过。批量扫描类任务会污染 LRU:一次性遍历把真正的热数据全挤成「旧」,LFU 对这种攻击免疫得多。
近似的艺术
教科书里的 LRU 要维护一条全局双向链表,每次访问都挪位置——百万级 key 的 Redis 扛不起这个开销。Redis 用的是近似 LRU:每个 key 记一个 24 位的最近访问时钟,淘汰时随机采样 N 个 key(默认 5 个),淘汰其中最久未用的。代价是可能漏掉「最该淘汰的」,换来的是零额外内存与 O(1) 的访问开销——工程上典型的够用哲学。LFU 更精细一点:8 位计数器记频率,用对数增长(访问几百万次才计满),再加时间衰减让长期不被访问的 key 计数自动下降,防止历史热点永久占坑。
怎么选
选型就两条主线。纯缓存实例:所有数据丢了都能从 DB 重建,选 allkeys 系——有批量扫描风险或访问冷热分明的选 allkeys-lfu,一般场景 allkeys-lru 足够;缓存与数据混用的实例:有些 key(如分布式锁、幂等标记)绝不能被提前驱逐,选 volatile 系并把这类 key 的 TTL 设为不过期,让淘汰永远避开它们。前提是这类 key 占比可控——volatile 系在「没设 TTL 的 key 占满内存」时会退化成 noeviction,同样报错。
还有一条工程纪律:maxmemory 要给 fork 与碎片留余量,设成物理内存的七成左右,配合内存使用率告警在 80% 时介入扩容——让淘汰策略做日常代谢,别让它做生死急救。淘汰解决「内存不够」,下一个要讲的是 Redis 自身的另外两套机制:key 怎么过期、主从怎么复制。
评论 (0)