线上 Redis 出问题,第一反应通常是查大 Key、查慢查询、查连接数。查了一圈都正常,最后发现是内存满了,而淘汰策略要么没配过,要么配了一个只在纸面上生效的。
更麻烦的是它不报错。写不进去的时候应用日志里可能什么都没有,读依然能读,监控上实例也还活着。
之前写 《Redis 大 Key 优化:205KB 的 String 该不该拆》 时提过内存这块,这篇单独把淘汰策略拎出来说。它不是一个参数问题。
大多数人把 maxmemory-policy 当成调优项,试几个值看哪个不报错。其实它回答的是一个业务问题:这个 Key 丢了,你能不能从别处再拼出来。
能拼出来,Redis 对你就是个代收点。包裹放三天没人取,店员清掉腾地方,反正寄件人手里还有底单,丢了再发一次就行。
拼不出来,Redis 对你就是个仓库。货架满了就锁门拒收,但里面一件都不会扔。
两种都合理,选了就得认。真正出事的是选完之后忘了自己选的是哪个——当代收点用,却往里存了没有底单的东西;当仓库用,货架满了还在怪系统不收货。
官方列了八种策略(allkeys-lru、allkeys-lfu、allkeys-random、volatile-lru、volatile-lfu、volatile-random、volatile-ttl、noeviction)。按失效后果归一下,其实只有三类。
第一类,所有 Key 都丢得起。allkeys-lru 按最近最少使用淘汰,allkeys-lfu 按访问频率淘汰,allkeys-random 纯随机。这是纯缓存场景,丢了回源数据库重建。访问分布集中的用 lfu 比 lru 更贴合热点,但差别要到一定量级才看得出来。
第二类,只有一部分 Key 丢得起。 带 volatile- 前缀的几种,淘汰范围只限带过期时间的 Key,没设 TTL 的它一个都不碰。适用的是混合实例:一部分是缓存,一部分是队列、锁、会话这类不能动的。
第三类,一个都不能丢。noeviction,满了直接拒绝写入。
分类清楚了,选型就不需要背表:先看你这个实例里有没有不能重建的 Key,有就不能用 allkeys 系列;再看这些 Key 有没有过期时间,没有就别碰 volatile 系列。
这是最容易翻车的一处。
volatile-* 系列的淘汰候选,只从设置了过期时间的 Key 里挑。如果这个实例里所有 Key 都没设 TTL,内存满了之后它一个候选都找不到,行为直接退化成 noeviction——拒写、报 OOM。
回到代收点的比喻:店员只清理贴了取件期限的包裹。可货架上全是从没贴过期限的长期寄存件,摆满了之后他没有任何东西可以清,只能在门口挂一块"暂停收件"。
你配置里明明写着 volatile-ttl,看起来是配了自动清理的。实际跑起来和 noeviction 一模一样。
判断方法很直接,看两个数:
# 当前策略和内存占用,确认是否真的顶到了上限redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human|maxmemory_policy"# 淘汰计数,长期为 0 说明淘汰压根没发生过redis-cli INFO stats | grep evicted_keysevicted_keys 一直是 0、used_memory 贴着 maxmemory、同时业务在报写失败,基本就是这个组合。
不配 maxmemory 就等于没有上限。64 位下默认值就是 0,不限制。Redis 会一直吃,直到把整机内存吃光,然后被系统的 OOM Killer 杀掉。
这跟"淘汰策略选错"是两回事:前者是容器没有边界,后者是边界内怎么腾地方。
主从切换之后策略也会变脸。副本默认不执行 maxmemory,只是接收主节点同步过来的数据。一旦主挂了、副本被提升为新主,它就开始按自己配置文件里的 policy 执行淘汰。
主节点配 allkeys-lru、从节点配 noeviction,切换那一刻淘汰行为就换了一套,而这个过程没有任何报错。所以副本的 policy 要跟主节点对齐。它不是备份配置,它是下一任主节点的配置。
不用急着改参数,先把这三问答完。
这个 Key 丢了,能从数据库重建吗? 能,就往 allkeys 系列走;不能,就留在 noeviction,等于承认它就是数据源。
有没有一类 Key 绝对不能动? 分布式锁、任务队列、会话,这三类被淘汰的后果不是"慢一点",是"两个 worker 同时进临界区"或者"任务凭空消失"。混在缓存实例里就是定时炸弹,条件允许就单独拆一个实例,关掉淘汰。
所有 Key 都有过期时间吗? 没有,volatile 系列就是纸面配置,回到上一节那个场景。
三问答完,配置本身没几行:
maxmemory 4gb # 按机器内存的 50%-70% 给,留出 fork 和缓冲区maxmemory-policy allkeys-lru # 纯缓存场景;混合实例另算maxmemory-samples 10 # LRU 采样数,默认 5,调大更准但更耗 CPU这里的 4gb 是示例值,不是推荐值。要按你的实例实际数据量和机器规格算,抄数字没有意义。
改策略可以不停机,CONFIG SET maxmemory-policy allkeys-lru 立刻生效,再改配置文件持久化。
但改之前一定要确认里面存的是什么。从 noeviction 切到 allkeys-lru,等于把"响亮的失败"换成了"安静的删除"。对一个纯缓存实例,这是修复;对一个混着队列的实例,这是把宕机换成了丢数据。
我整理过的一个案例里,一个 6 主 6 从的电商集群,凌晨主节点所在的物理机内存耗尽触发切换,切换后大约 200 个用户的购物车数据没了。
直接原因有三层:
cluster-require-full-coverage 关着,故障期间没受影响的槽位继续可写,跨槽位的购物车写入一半成功一半失败看着像集群配置问题,根子还是同一个判断:把 Redis 当成了权威存储。 购物车这种数据一旦以 Redis 为准,任何一次切换、任何一次淘汰,丢的都是真数据。反过来,如果 MySQL 是权威、Redis 只做缓存层,上面那三层原因最多让页面慢几秒。
内存淘汰策略只是这个判断的一个出口。你在配 maxmemory-policy 那一刻做的选择,和你决定购物车存哪儿,是同一个问题的两种问法。
淘汰策略没有最优值,只有和你给这个实例的定位对不对得上。判断标准就一条:这个 Key 丢了能不能重建。配之前先答完那三个问题,比试遍八种策略快得多。
你手上那个 Redis,是代收点还是仓库?如果混着用,现在是拿什么兜底的?评论区说说场景,我看看有没有更省事的拆法。