跳转到内容

过期与时间轮

Caffeine 把过期时间存入节点,在读、写、维护和可选 scheduler 触发时清理;动态 TTL 用时间轮降低调度成本。

先给答案:Caffeine 把过期时间存入节点,在读、写、维护和可选 scheduler 触发时清理;动态 TTL …

Section titled “先给答案:Caffeine 把过期时间存入节点,在读、写、维护和可选 scheduler 触发时清理;动态 TTL …”

Caffeine 把过期时间存入节点,在读、写、维护和可选 scheduler 触发时清理;动态 TTL 用时间轮降低调度成本。 理解过期与时间轮时,要先确认入口与状态归属,再跟踪控制流或数据流的推进顺序,最后落到对外可观察的结果。

主要失效边界集中在“没有 scheduler、Expiry 返回 0、refresh 与 expire 同时配置”这些场景。它们破坏的是容量、顺序、并发或生命周期前提;排查时应先确认状态是否仍由正确对象持有,再核对推进条件和清理路径。

主要失效边界集中在“没有 scheduler、Expiry 返回 0、refresh 与 expire 同时配置”这些场景。它们破坏的是容量、顺序、并发或生命周期前提;排查时应先确认状态是否仍由正确对象持有,再核对推进条件和清理路径。

create/update/read -> variableTime -> fixed queues / TimerWheel
-> recheck -> remove/reschedule

固定策略配置在 Caffeine.java:694、759,动态策略在 805;时间计算在 BoundedLocalCache.java:1436、1455、1476;expireEntries 在 858,expireVariableEntries 在 960。

为什么不用每条目一个定时任务

Section titled “为什么不用每条目一个定时任务”

替代方案:每次插入提交一个 ScheduledFuture。 为什么不行:大量短 TTL 条目带来任务对象、取消成本和队列竞争。 证据:TimerWheel(TimerWheel.java:40)用分层时间桶保存节点并批量推进。

refreshAfterWrite 在 Caffeine.java:879 配置,是重新加载而不是立即删除;expireAfterWrite 则让值失效。刷新判断在 BoundedLocalCache.java:1293、1303。

场景 原因 规避
没有 scheduler 物理删除滞后 空间敏感时配置 scheduler
Expiry 返回 0 条目快速失效 明确 TTL 语义
refresh 与 expire 同时配置 新值未完成就失效 给加载留出余量

时间轮适合海量定时任务、连接超时、会话过期和重试调度,应权衡精度与管理成本。

面试锚点

  • 过期是主动还是被动?
  • TimerWheel 为什么需要多层?
  • refreshAfterWrite 与 expireAfterWrite 有何区别?