volatile-ttl不计算绝对时间,只比较采样key的剩余TTL值;因剩余TTL惰性更新且采样随机,当大量key的TTL扎堆(如全在118~122秒),策略失去区分力,导致淘汰低效或抖动。

volatile-ttl 不是“计算绝对时间出错”,而是根本没算绝对时间——它只比剩余 TTL,且依赖随机采样,误差来自设计取舍,不是实现 bug。
volatile-ttl 的淘汰逻辑根本不涉及绝对时间戳比较
Redis 淘汰时不会拿 expires 字典里存的过期时间戳(比如 1760847600000)和当前系统时间做减法再比大小。它直接读取每个采样 key 的剩余 TTL 值(单位秒/毫秒),然后在候选集里挑最小的那个。这个“剩余 TTL”是 Redis 在每次访问或定期扫描时动态算出来的缓存值,并非实时重算。所以问题不在“计算不准”,而在于:
– 剩余 TTL 是惰性更新的,可能滞后几毫秒到几百毫秒
– 采样本身是随机的,20 个 key 里如果全剩 120±1 秒,挑谁都一样,策略失去区分力
系统时钟漂移和主从时钟不同步会放大误差表现
当 Redis 实例部署在虚拟机、容器或跨可用区节点上时,time() 或 clock_gettime(CLOCK_MONOTONIC) 返回值可能因 NTP 调整、CPU 频率缩放或 hypervisor 抢占而跳变。这会导致:
– 同一个 key,在 master 上查 TTL 返回 119,在 slave 上返回 122(因为时钟慢了)
– 主从复制过程中,EXPIREAT 命令带的是绝对时间戳,但 slave 执行时用本地时间判断,若时钟差 >1 秒,就可能提前或延后过期
– volatile-ttl 淘汰只发生在本地内存满时,不跨节点协调,所以各节点对“谁该先死”的判断天然不一致
为什么你看到的“误差”其实是 TTL 扎堆 + 采样失效
真正让你觉得“时间不准”的现象,几乎都源于业务写入时未加扰动:
– 批量刷新缓存(如 Dify 启动、Celery Beat 定时任务)集中执行 EXPIRE key 3600,导致成千上万个 key 的剩余 TTL 全落在 118~122 区间
– volatile-ttl 每次采样 20 个 key,发现其中 18 个 TTL 都是 120,无法排序,只能随机选一个删,看起来像“乱删”
– 更糟的是,刚删掉一个,新写入的 key 又被设成同样 TTL,形成“删了又来、来了又删”的抖动循环
– 这不是时间计算误差,是淘汰策略在输入数据缺乏梯度时的自然退化
最常被忽略的一点:volatile-ttl 的“误差感”往往出现在监控里 evicted_keys 突增但 expired_keys 几乎为 0 的时刻——说明不是过期机制在干活,而是内存压力触发了淘汰,而淘汰又因 TTL 扎堆变得低效。这时候调 hz 或 active-expire-effort 没用,得回业务层打散 TTL。


















