Redis的内存淘汰策略由maxmemory参数触发,它决定实例在内存占用达到上限后如何处理新的写命令。只设置maxmemory而不配置maxmemory-policy,会沿用默认的noeviction策略,表现为写入失败、缓存突然失效。要真正控制内存,需要从淘汰范围、筛选算法两个维度理解八种策略,并结合业务访问模型做选择。

当Redis执行SET、LPUSH、SADD等可能增加内存占用的命令时,会先检查当前已用内存是否超过maxmemory。如果没有超过,直接执行;如果已经达到上限,就根据maxmemory-policy选择若干键删除,直到能够满足本次写入需求。默认的noeviction策略不会删除数据,只会返回错误,这也是很多线上写入异常的原因之一。
一、八种内存淘汰策略的原理拆解
Redis目前提供了八种内存淘汰策略,名称中的allkeys表示从所有键中选取候选键,volatile表示只从设置了过期时间即TTL的键中选取。两者混用时要特别注意:如果实例中大量键没有设置TTL,volatile系列策略即使触发,也可能找不到足够多的键来释放内存,最终仍然返回OOM。
具体策略可以分为四类。LRU策略选择最近最少使用的键,Redis并不是维护一条精确的双向链表,而是利用对象头中的lru字段记录最后一次访问时间,每次淘汰时随机采样maxmemory-samples个键,删除其中空闲时间最长的那个。这种方式牺牲了一定精度,但避免了维护全局链表的额外开销。LFU策略从Redis 4.0开始引入,在lru字段上复用了访问频率计数,计数会随着时间衰减,目的是优先删除访问频率最低的键。random策略随机删除,ttl策略则直接比较剩余存活时间。
| 策略名称 | 淘汰范围 | 筛选依据 | 适用方向 |
|---|---|---|---|
| noeviction | 无 | 不淘汰 | 禁止丢失数据 |
| allkeys-lru | 所有键 | 最近最少使用 | 普通缓存 |
| volatile-lru | 设置了TTL的键 | 最近最少使用 | 缓存和永久数据混合 |
| allkeys-lfu | 所有键 | 访问频率最低 | 热点明显的缓存 |
| volatile-lfu | 设置了TTL的键 | 访问频率最低 | 热点明显且保留永久键 |
| allkeys-random | 所有键 | 随机选择 | 键价值相近 |
| volatile-random | 设置了TTL的键 | 随机选择 | 过期键价值较低 |
| volatile-ttl | 设置了TTL的键 | 剩余存活时间最短 | 希望删除即将过期键 |
默认的近似LRU采样数量由maxmemory-samples参数控制,默认值为5。采样数量越大,淘汰结果越接近真实LRU,但每次淘汰时消耗的CPU也会增加。LFU则依赖另外两个参数:lfu-log-factor控制访问频率计数的增长速度,lfu-decay-time控制频率每分钟的衰减幅度。理解这些底层机制,才能解释为什么有些策略在特定负载下表现不如预期。
二、从场景出发选择内存淘汰策略
如果Redis只作为缓存使用,并且业务数据可以从数据库重新加载,一般优先选择allkeys-lru或allkeys-lfu。两者的核心区别在于:LRU关注最近一次访问距离现在多久,LFU关注一段时间内被访问了多少次。对于商品详情、新闻内容这类读取相对均匀的场景,allkeys-lru足够稳定;对于榜单、热门内容这类访问频率差异明显的场景,allkeys-lfu更容易保留真正的高频热点。
如果Redis中同时保存了需要持久保留的键和可以淘汰的缓存键,不要急着用allkeys系列,否则永久数据也可能被删除。这时可以使用volatile-lru或volatile-lfu,但必须保证所有缓存键都设置了合理的TTL。会话token、验证码这类本身就有过期时间的键,使用volatile-ttl可以优先删除即将到期的数据,减少对仍有较长有效期的会话的影响。
# Redis 缓存实例示例配置 maxmemory 2gb maxmemory-policy allkeys-lfu maxmemory-samples 10 lfu-log-factor 10 lfu-decay-time 1
上面配置适合热点访问明显的纯缓存实例。maxmemory设置为2gb,策略选择allkeys-lfu,采样数提高到10,可以让淘汰判断更接近真实访问频率。lfu-log-factor越大,访问次数增长越慢,计数器越能区分高频键;lfu-decay-time设为1表示每分钟对频率计数做一次衰减,避免历史热度永远占据优势。
如果业务写入压力不大,但对数据丢失极度敏感,可以考虑使用noeviction,并配合另一个只保存缓存的Redis实例。这样持久化数据实例绝不会因为内存压力而删除键,缓存实例即使淘汰全部数据也不会影响核心业务。这种方式会增加部署成本,但在订单、账户、配置中心等场景中很有价值。
三、常见问题与注意事项
最容易踩到的坑是只配置maxmemory,却没有修改maxmemory-policy。默认策略是noeviction,当内存达到上限后,写入命令会返回OOM错误,而读取命令仍然正常。如果监控只看接口成功率,可能误以为是数据库或网络问题。上线前应该通过CONFIG GET maxmemory-policy确认当前策略,而不是只检查maxmemory数值。
另一个高频问题是使用volatile-lru、volatile-lfu或volatile-ttl时,大量键没有设置过期时间。Redis只会从设置了TTL的键中挑选淘汰对象,如果这类键数量很少,哪怕实例中其他键占用了大量内存,也不会被删除,最终写入仍会失败。要使用volatile系列策略,就必须在写入缓存键时同步设置EXPIRE、PEXPIRE或使用SETEX命令。
主从复制架构下,内存淘汰策略还可能带来主从切换后的行为差异。主库淘汰键后会向从库同步DEL命令,从库按照复制链路执行删除,这本身不会让从库主动淘汰数据。但如果从库被提升为主库,而它自己的maxmemory-policy配置与旧主库不一致,新的写入可能触发完全不同的淘汰结果。因此建议主从实例统一配置相同的maxmemory和maxmemory-policy,避免切换后出现写失败或大面积误删。
AOF重写和持久化也可能影响内存表现。AOF重写会通过写时复制创建子进程,重写期间主库对键的修改会让父子进程共享的内存页发生复制,导致实际物理内存短暂上升。即使Redis的used_memory没有超过maxmemory,RSS依然可能超过限制。如果服务器没有预留足够内存,操作系统的OOM killer可能会触发。内存碎片同理,maxmemory只基于used_memory计算,碎片的增长不会直接触发淘汰,却会让RSS持续膨胀。
四、验证淘汰策略与调优建议
线上调整策略时,不建议直接修改配置文件后重启,可以先通过redis-cli动态修改并观察效果。CONFIG SET命令即时生效,确认无误后再执行CONFIG REWRITE将配置写回redis.conf。下面是一个临时压低内存上限并测试allkeys-lru效果的示例。
redis-cli CONFIG SET maxmemory 5mb redis-cli CONFIG SET maxmemory-policy allkeys-lru redis-cli CONFIG SET maxmemory-samples 10 redis-cli CONFIG REWRITE
执行写入压测后,可以通过INFO stats观察evicted_keys字段。如果该数值持续增长,说明淘汰策略正在生效;如果写入大量键后evicted_keys仍然为0,则要检查maxmemory是否设置得过低、策略是否为noeviction,或者是否所有键都没有过期时间导致volatile系列无法选择候选键。
for i in $(seq 1 10000); do redis-cli set key:$i value:$i > /dev/null done redis-cli INFO stats | grep evicted_keys
除了evicted_keys,还可以关注keyspace_hits和keyspace_misses。如果淘汰策略设置合理,缓存命中率应该保持稳定;如果命中率明显下降,可能是allkeys-random或采样数过低导致高频键被误删。对于LFU策略,若发现新写入的键很快被淘汰,可以适当降低lfu-log-factor,让初始访问频率增长更快一些;如果热点反而被低频率数据挤掉,则可以提高lfu-log-factor或减小lfu-decay-time。
最后需要明确,内存淘汰策略解决的是达到maxmemory上限后的写入问题,它不能替代合理的TTL设计。大量过期键如果不被访问,Redis不会立即释放它们占用的内存,过期删除和内存淘汰是两套独立机制。评估内存压力时,应同时查看used_memory、used_memory_rss、maxmemory以及evicted_keys,才能准确判断是否需要调整策略、提高maxmemory,或者从应用层减少缓存体量。
Redis内存淘汰策略缓存淘汰算法Redis配置优化修改时间:2026-09-21 08:06:34