下单时把状态放进 Redis,顺手给了两分钟过期。支付回调把值改成 paid,看起来也正常。再查一次 TTL,原来在倒数的 key 却变成了 -1。

要是你在缓存里看到这种 key,先翻最后一次写入。问题可能就出在那个平平无奇的 SET。

先拿一个测试 key 看一眼

用 Redis 6.0 或更新版本,打开测试实例的 redis-cli,输入下面四行。示例会改动这一个测试 key,请用自己的测试前缀。

SET java-li:ttl-demo ready EX 120
TTL java-li:ttl-demo
SET java-li:ttl-demo paid
TTL java-li:ttl-demo

第一次 TTL 是剩余秒数,刚设置时接近 120。第二次是 -1,表示 key 还在,过期时间已经没了。普通 SET 成功覆盖原值时会清掉已有的过期时间。要是看到 -2,说明检查那一刻已经没有这个 key,可能它刚好过期了。

更新状态时,你要从哪一刻算时间

比如这条状态只允许从第一次写入起保留两分钟。更新时用 KEEPTTL,就能沿用原来的到期时刻。XX 让这次更新只在 key 存在时发生,避免回调迟到后又把它建回来。

SET java-li:ttl-demo ready EX 120
SET java-li:ttl-demo paid XX KEEPTTL
TTL java-li:ttl-demo

读到的秒数会接着往下走。要是 key 在更新前已经过期,带 XX 的 SET 不会写入。还有一种容易漏掉的情况,旧 key 本来就是永久的,KEEPTTL 会照样保留这个状态。所以修完写入逻辑,已经没有 TTL 的旧数据还得按业务规则单独处理。

如果你的约定是每次更新都重新留两分钟,就在写入时直接带 EX 120。允许缺失时补建才省略 XX。

SET java-li:ttl-demo paid XX EX 120
TTL java-li:ttl-demo

最后这组会让已有 key 从本次更新重新倒数。示例 key 随后会自行过期。

回到项目里,测两种回调

找到 Java 代码里真正覆盖值的那一行。可能是某个状态更新方法,也可能是封装好的缓存工具。看实际发出的 Redis 命令有没有带对应选项。先写入带 TTL 的值,等几秒走一次更新,检查剩余时间是否按约定延续或重算。再让 key 先过期,走一次迟到的回调,确认是否允许重建。

能用一条 SET 带上到期规则,就别拆成先写值再补 EXPIRE。两条命令之间,连接一断或另一个请求一插队,key 的状态就可能和你想的不一样。需要分辨命令到底有没有送到 Redis 时,可以先排建连和客户端等待。 Spring Boot Redis 连接超时怎么排查

参考资料