Redis 过期删除和内存淘汰不是一回事:expire、TTL、maxmemory 到底谁负责
很多人学 Redis 时,会把两个概念混在一起:
- key 过期了,Redis 怎么删?
- Redis 内存满了,Redis 怎么淘汰?
它们看起来都在“删 key”,但解决的问题完全不同。
一句话先分开:
过期删除处理的是:这个 key 到时间了,是否应该失效。
内存淘汰处理的是:Redis 内存不够了,应该牺牲哪些 key 腾空间。
前者和expire/TTL有关,后者和maxmemory/maxmemory-policy有关。
如果这两个概念不分清,面试和排查线上问题都会很容易乱。
过期删除:key 到时间了怎么办
Redis 可以给 key 设置过期时间。
比如:
SET token:1001 abc
EXPIRE token:1001 3600
意思是:
token:1001 这个 key 最多存活 3600 秒。
也可以直接写:
SET token:1001 abc EX 3600
查看剩余时间:
TTL token:1001
返回值大概有三类:
- 正数:还剩多少秒。
-1:key 存在,但没有过期时间。-2:key 不存在。
这里的核心是:Redis 不一定在过期时间到达的那一瞬间立刻删除 key。
它通常结合两种方式:
- 惰性删除。
- 定期删除。
惰性删除:访问时发现过期再删
惰性删除很好理解。
当客户端访问某个 key 时,Redis 先检查它是否过期。
如果没过期,就正常返回。
如果已经过期,就删除它,并表现得像 key 不存在。
比如:
GET token:1001
Redis 发现token:1001已经过期,于是删除,然后返回空。
这种方式的优点是简单,不会为了删除过期 key 一直扫描全库。
缺点也明显:如果一个过期 key 再也没人访问,它可能会继续留在内存里一段时间。
所以 Redis 不能只靠惰性删除。
定期删除:后台抽样清理过期 key
Redis 还会周期性地检查一部分设置了过期时间的 key。
注意这里是“一部分”,不是每次把所有 key 都扫一遍。
为什么?
因为 Redis 通常要服务大量请求,如果每隔一段时间就全量扫描所有 key,会造成明显卡顿。
所以 Redis 的策略更像:
定期抽样检查一批带过期时间的 key,发现过期就删除;如果过期比例较高,再继续多扫一点。
这是一种折中:
- 不让过期 key 永远堆在内存里。
- 也不为了清理过期 key 把 Redis 主线程拖死。
所以过期删除的本质是:
用惰性删除保证访问正确性,用定期删除控制内存里的过期 key 数量。
内存淘汰:maxmemory 到了怎么办
过期删除不等于内存淘汰。
内存淘汰发生在 Redis 内存达到限制时。
比如配置了:
maxmemory 2gb
当 Redis 使用内存接近或超过这个限制时,如果还要写入新数据,就要根据策略选择一些 key 淘汰掉。
策略由:
maxmemory-policy
决定。
常见策略包括:
noeviction:不淘汰,写入直接报错。allkeys-lru:从所有 key 中按 LRU 倾向淘汰。volatile-lru:只从设置了过期时间的 key 中按 LRU 倾向淘汰。allkeys-random:从所有 key 中随机淘汰。volatile-random:只从设置了过期时间的 key 中随机淘汰。volatile-ttl:优先淘汰 TTL 更短的 key。allkeys-lfu:从所有 key 中按 LFU 倾向淘汰。volatile-lfu:只从设置了过期时间的 key 中按 LFU 倾向淘汰。
这里的重点不是背策略名字,而是理解两个维度。
第一个维度:候选范围。
allkeys-*:所有 key 都可能被淘汰。volatile-*:只有设置过期时间的 key 可能被淘汰。
第二个维度:淘汰标准。
lru:偏向淘汰最近较少使用的 key。lfu:偏向淘汰访问频率较低的 key。random:随机淘汰。ttl:偏向淘汰更快过期的 key。
两者最大的区别
可以放在一张表里看。
| 对比点 | 过期删除 | 内存淘汰 |
|---|---|---|
| 触发原因 | key 到期 | Redis 内存达到 maxmemory |
| 依赖配置 | expire / pexpire / SET EX | maxmemory / maxmemory-policy |
| 删除对象 | 已经过期的 key | 策略选中的 key,不一定过期 |
| 目标 | 保证过期语义 | 腾出内存 |
| 常见机制 | 惰性删除 + 定期删除 | LRU/LFU/TTL/random/noeviction |
最容易误解的一点是:
被内存淘汰的 key 不一定已经过期。
如果配置的是allkeys-lru,一个没有设置过期时间的热点之外 key,也可能因为内存压力被淘汰。
反过来:
已经过期的 key 也不一定在过期瞬间立刻从内存里消失。
它可能等到访问时被惰性删除,或者等后台定期删除抽样扫到。
为什么线上会出现“明明设置 TTL,内存还是高”
这是很常见的问题。
原因可能有几类。
第一,很多 key 没有设置 TTL。
可以抽样检查:
TTL some:key
如果返回-1,说明这个 key 没有过期时间。
第二,过期 key 没有立刻被清掉。
这不一定是 bug。Redis 的过期清理本来就不是“到点全量删除”。
第三,写入速度太快,过期速度跟不上写入速度。
比如大量短 TTL key 持续写入,定期删除虽然一直工作,但内存仍可能上升。
第四,maxmemory 没有限制,或者淘汰策略不符合业务。
如果没有设置maxmemory,Redis 会尽量用系统内存,直到操作系统压力变大。
如果设置了volatile-lru,但很多 key 没有 TTL,那么这些无 TTL key 根本不在淘汰候选范围内。
这就是为什么缓存场景里经常更推荐:
allkeys-lru
因为缓存数据本来就允许被淘汰,候选范围覆盖所有 key,更符合“内存不够就丢缓存”的模型。
排查时看哪些指标
如果你怀疑 Redis 内存或过期策略有问题,可以先看:
INFO memory
INFO stats
CONFIG GET maxmemory
CONFIG GET maxmemory-policy
重点关注:
used_memory:Redis 当前使用内存。maxmemory:是否设置内存上限。evicted_keys:因为内存淘汰被删除的 key 数量。expired_keys:因为过期被删除的 key 数量。keyspace_hits/keyspace_misses:命中和未命中情况。
判断思路可以这样走:
如果expired_keys增长,说明过期删除在发生。
如果evicted_keys增长,说明发生了内存淘汰。
如果used_memory很高,但evicted_keys一直是 0,要看是否没配置maxmemory,或者策略是noeviction。
如果maxmemory-policy是volatile-*,但很多 key 没有 TTL,那么内存淘汰范围可能比你想象的小。
面试里怎么回答
如果被问“Redis 过期删除和内存淘汰有什么区别”,可以这样回答:
过期删除是 key 设置了 TTL 后,到期需要失效,Redis 通过惰性删除和定期删除结合处理。惰性删除是在访问 key 时发现过期再删,定期删除是后台抽样清理过期 key。内存淘汰是 Redis 达到 maxmemory 后,为了腾空间按 maxmemory-policy 选择 key 删除,比如 allkeys-lru、volatile-lru、allkeys-lfu 等。过期删除处理时间语义,内存淘汰处理容量压力,被淘汰的 key 不一定过期。
如果继续追问“为什么 key 过期了内存没马上降”,可以这样答:
Redis 不会在 key 过期瞬间全量扫描删除,因为这样成本太高。过期 key 可能等到下一次访问时惰性删除,也可能被后台定期删除抽样清理。所以过期时间保证的是逻辑失效,不保证内存立刻下降。
总结
这两个概念一定要分开:
expire/TTL管的是“什么时候失效”。maxmemory-policy管的是“内存不够时丢谁”。- 过期删除主要靠惰性删除和定期删除。
- 内存淘汰主要看
maxmemory和maxmemory-policy。 expired_keys增长代表过期删除,evicted_keys增长代表内存淘汰。
一句话收尾:
过期删除是时间问题,内存淘汰是容量问题。它们都可能删除 key,但不是同一套机制。