登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis 过期键区分主动过期与访问时删除的实现方法

来源:17golang原创

时间:2026-09-19 23:07:29 324浏览 收藏

线上运维缓存的时候经常碰到这类怪事:监控里看键的剩余存活时间还没到0,下次请求过来读这个键直接返回nil,甚至有时候跨节点统计过期键的数量对不上,排查半天找不到手动删键的痕迹。这类现象本质上就是Redis对过期键用了两套独立的删除逻辑,区分后台主动过期清理、和用户访问键时才触发的被动删除,两种机制并行跑才会出现我们感知上的"数据不一致"。

缓存排查里最容易误判的一幕是:刚给 Redis 键设置了过期时间,过一会儿再看却发现键不见了;另一个低频键则像“过期了还占着位置”。这通常不是 EXPIRE 失效,而是 Redis 同时采用了访问时处理和后台主动清理两条路径。

官方文档地址:https://redis.io/docs/latest/commands/expire/

判断过期键先看 TTL:正数表示仍有剩余时间,-1 表示键存在但没有过期时间,-2 表示键不存在。不要把“TTL 到零的瞬间”和“后台删除完成的瞬间”当成同一件事。
要点速览
  • 客户端访问到已经到期的键时,Redis 会在访问路径上处理它。
  • 没有再次访问的过期键,由后台周期性抽样清理,删除时刻不是业务定时器回调。
  • TTL 适合秒级判断,PTTL 适合毫秒级排查;-1-2 的含义完全不同。

先固定 TTL 的三种返回值

排查时不要先用业务复杂的 key。先建立三个最小状态:一个带 10 秒过期时间的键、一个永久键、一个不存在的键。下面的命令是示例检查顺序,注释说明每条命令想确认什么。

# 创建一个短期键,便于观察剩余时间
redis-cli SET demo:expire "v"
# 设置 10 秒超时;返回 1 代表设置成功
redis-cli EXPIRE demo:expire 10
# 返回剩余秒数,通常是 9 到 10 之间
redis-cli TTL demo:expire

# 创建一个没有过期时间的永久键
redis-cli SET demo:persistent "v"
# -1 表示键存在,但没有关联 expire
redis-cli TTL demo:persistent

# 检查不存在的键;-2 与 -1 不要混淆
redis-cli TTL demo:missing

表格可以把结果直接固定下来:

TTL 结果键状态排查含义
大于等于 0存在过期时间仍在倒计时,数值会随时间减少
-1键存在但无过期时间检查是否被 SET、RENAME 或 PERSIST 改成持久键
-2键不存在可能已过期、被 DEL,或根本没有创建成功
Redis TTL 正数、负一和负二对应三种过期键状态的结构说明图
图1:TTL 返回值与 Redis 过期键状态的结构说明图,不是截图或运行证据。

主动过期与访问时删除的分工

Redis 官方把过期处理分成两种方式。第一种是访问时处理:客户端执行读取或其他会触碰该 key 的命令,Redis 发现它已经超时,就把它视作过期并完成删除。第二种是主动过期:后台周期性从带过期时间的键中抽样,把已经到期的键清掉。

两条路径解决的是不同问题。访问时处理保证“读到过期值”这件事不会继续发生;主动清理则负责处理那些再也不会被访问的冷键。如果只依赖访问,冷键会长期占用过期索引和内存;如果只依赖后台逐个扫描,又会带来不必要的扫描成本。因此,低频键在过期后短暂留存于内部结构,并不等于业务读取会拿到旧值。

Redis 过期键从客户端访问与后台抽样两条路径进入删除处理的结构说明图
图2:主动过期与访问时删除的双路径结构说明图,不是 Redis 实例截图。

按低干扰顺序排查业务缓存

真实故障里最重要的是先记录状态,再做读取。读取本身可能触发访问时处理,所以不要只凭一次 GET 后看到的 nil 推断后台已经扫过该键。

# 先检查剩余时间,保留原始判断依据
redis-cli TTL cache:user:42
# 用 PTTL 补充毫秒级剩余时间,适合临界点排查
redis-cli PTTL cache:user:42
# 再判断 key 是否仍存在;-1、-2 的语义仍以 TTL 为准
redis-cli EXISTS cache:user:42
# 最后读取值;到期键可能在这一步被访问时处理
redis-cli GET cache:user:42

如果第一次 TTL 返回正数,紧接着 GET 返回空,先考虑两个可能:键在两条命令之间自然到期,或者客户端访问的是不同数据库、不同节点。若 TTL 返回 -1,不要继续等待过期,而要追查写入逻辑是否用不带 EX 选项的 SET 覆盖了原键;官方文档也说明,覆盖内容的命令可以清除原有超时。

时间精度和边界要单独记录

TTL 返回秒,适合业务级观察;PTTL 返回毫秒,适合判断是否正在跨过过期临界点。Redis 的过期信息以绝对时间保存,实例重启或加载持久化数据时,机器时钟仍然会影响“现在是否已到期”的判断,因此排查跨机器问题时要同时记录节点和系统时间。

复制场景也不要把副本上的观察等同于主节点的过期动作。常规复制中,过期由主节点处理并传播删除效果,副本等待主节点的删除操作。缓存业务只需要记住一个实用边界:过期策略决定“何时不再向客户端提供该键”,不承诺每个键都在同一毫秒从所有内部结构中消失。

最终检查清单可以压缩为:先 TTL,再用 PTTL 观察临界点;确认数据库和节点;检查写入命令是否覆盖或 PERSIST;最后才用 GET 验证业务值。

相关问题

TTL 返回 -1 是不是马上就会过期

不是。-1 表示键存在但没有过期时间,通常需要检查写入、覆盖或 PERSIST 逻辑。

TTL 返回 -2 能证明键是被过期删除的吗

不能。-2 只表示当前键不存在,也可能是 DEL、覆盖其他数据库或创建失败造成的。

过期时间到了,为什么内存监控不会同步下降

键会先在访问路径或主动抽样中被处理,内存分配器和监控采样又有自己的回收与刷新节奏,二者不是同一时刻。

什么时候应该使用 PTTL

当问题发生在秒级临界点、需要判断两个命令之间是否已经跨过到期时刻时,使用 PTTL;普通缓存巡检使用 TTL 即可。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>