🛒
shop.example.com 的购物车、session 和商品缓存还在一台 Redis 上膨胀。监控开始报警:used_memory 逼近上限。本篇只回答:触顶后谁有资格被删、副本为何常常「看起来没在淘汰」,以及为什么淘汰解决不了单机容量上限。落盘见 持久化,主从与自动切主见 复制 / Sentinel,装不下再水平切开见 Cluster 哈希槽。缓存穿透 / 击穿 / 雪崩与 Cache-Aside 不在本篇范围。
系列压力阶梯(本篇钉住「淘汰」这一格):

内存报警时,是 OOM 崩掉还是开始丢 key?

App 持续 SET / HSET,Redis 进程的已用内存往上爬。你配置了 maxmemory(服务端允许数据占用的内存上限)之后,触顶时会发生什么,并不取决于「机器还剩多少物理 RAM」,而取决于 maxmemory-policy(触顶后的内存淘汰策略)。
两条分叉很清楚:
  1. 拒写:策略是 noeviction(或不淘汰候选集为空)时,新的写命令失败,已有 key 尽量留下。
  1. 删 key:策略允许淘汰时,Redis 按规则挑出受害者删掉,腾出空间后再接受写。
还有一个运维边界:复制积压、持久化等缓冲区占用不计入maxmemory 的比较。因此上限要留余量——把 maxmemory 顶满整机可用内存,仍可能被缓冲区推到真实 OOM。
触顶不是「机器必崩」,而是「按你设的策略:拒写,或删谁」。

allkeys 和 volatile 差在「谁有资格被删」

先看主载体:八种常用策略,比散文更省事。
policy
谁有资格被删
触顶行为
noeviction
无人
拒写;不主动删 key
allkeys-lru
全部 key
近似 LRU:优先删「最久没被访问」的
allkeys-lfu
全部 key
LFU:优先删「访问最不频繁」的
allkeys-random
全部 key
在全部 key 里随机删
volatile-lru
仅带 expire 的 key
在带过期时间的集合里做近似 LRU
volatile-lfu
仅带 expire 的 key
在带过期时间的集合里做 LFU
volatile-random
仅带 expire 的 key
在带过期时间的集合里随机删
volatile-ttl
仅带 expire 的 key
优先删「更接近过期」的
本地定义两句就够用:
  • allkeys 系策略:候选集是整库 key;购物车、无 TTL 的 session、商品缓存都可能被删。
  • volatile 系策略:候选集只含设置了过期时间(expire)的 key。若库里没有任何带 expire 的 key,volatile-* 的行为会接近 noeviction——你以为会淘汰,实际却在拒写。
近似 LRU(Least Recently Used,最近最少使用)不会扫全库,而是用采样(maxmemory-samples)估「谁更冷」。LFU(Least Frequently Used,最不经常使用)从 Redis 4.0 起可用,可用 lfu-log-factor / lfu-decay-time 调「多热才算热」。官方把 allkeys-lru 当作多数「访问服从帕累托分布」缓存场景的合理默认:少数热 key 留下,长尾冷 key 先让路。
Redis 8.6+ 的 LRM,以及怎么用命中率调策略?(非主结论)
从 Redis 8.6 起还有 allkeys-lrm / volatile-lrm(Least Recently Modified,按写侧时间戳偏向更久未改的 key)。版本不够就不要当通用建议。调策略时看 INFOkeyspace_hits / keyspace_missesevicted_keys:命中率差、淘汰猛,说明候选集或算法与访问形态不匹配。也可用 redis-cli --hotkeys 抽样看热点(本身不是容量扩容方案)。

副本为什么常常「看起来没在淘汰」?

你在 master 上看到 evicted_keys 在涨,副本上却「安静」——这往往不是监控坏了。
⚠️
默认情况下,副本会忽略自己的 maxmemory,不在本地独立跑淘汰。主节点按策略删 key 后,会把合成的 DEL 复制给副本,副本跟着删。若把 replica-ignore-maxmemory 设为 no,副本才会按自己的 maxmemory 参与淘汰——这通常只适合可写副本或你明确接受主从可能短暂不一致、且写入幂等的场景,改之前要搞清楚后果。即便配置了 maxmemory,副本仍可能用掉比该上限更多的内存——要盯副本真实占用,避免物理机 OOM,而不能只看「策略名」。
因果链是:App 写 master → master 触顶并淘汰 → 复制流带上删除 → 副本数据集跟随。副本自己不「另起一套 LRU」,所以仪表盘上常常「主在淘汰、从在跟」。

淘汰解决不了单机容量上限

淘汰回答的是:这一台内存顶满时,拒写还是丢谁。它不把键空间摊到多机,也不提高「整库还能装多少不相关的热数据」。
shop.example.com 若工作集已经稳定大于单机可承载(扣掉缓冲区余量之后),换 allkeys-lru 只会更频繁地删仍可能被访问的 key,换来更高的后端穿透与抖动——工作集并没有变小。要水平切开键空间,走到 Cluster 的 16384 哈希槽,而不是继续在单进程里赌淘汰算法。
淘汰是单机泄压阀;分片才是容量扩容。

清单:触顶前确认的配置

因果收束:App 写入撑大内存 → 碰到 maxmemorymaxmemory-policy 决定拒写或在「全库 / 仅带 TTL」候选集里删 key → 默认由 master 淘汰并复制 DEL,副本不独自淘汰且可能超过 maxmemory → 淘汰管不了「数据集已经装不进一台」——那是分片问题。
上线前自检:
已设置 maxmemory,并为复制 / 持久化等缓冲区留出主机余量
maxmemory-policy 与业务一致:缓存可丢用 allkeys;必须保住无 TTL 键时慎用 volatile 或改用拒写
若选 volatile-*,确认关键键确实带 expire;否则行为接近 noeviction
监控 used_memoryevicted_keyskeyspace_hits / keyspace_misses,以及副本真实内存,防物理 OOM
默认副本忽略 maxmemory 已理解;仅在明确需要时改 replica-ignore-maxmemory
工作集长期大于单机上限时,转向分片(见 Cluster 篇),而不是只换淘汰策略
Sources
  • Evidence rechecked: 2026-08-15
  • Key evictionmaxmemory / maxmemory-policyvolatile-* 在无 expire key 时接近 noevictionallkeys-lru 与帕累托场景;缓冲区不计入 maxmemory 比较并需留 RAM;近似 LRU 与 maxmemory-samples;LFU(≥4.0)与 lfu-log-factor / lfu-decay-time;LRM(≥8.6,仅扩展阅读);INFO 的 hits/misses / evicted_keys
为什么割下的手指无法解锁现代手机?Redis 扩容靠的不是一致性哈希:16384 个槽如何托住水平扩展?
Loading...