恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

JCache缓存过期策略:TTL与TTI的关键差异与实战解析

  • 首页
  • 资讯中心
  • /
  • JCache缓存过期策略:TTL与TTI的关键差异与实战解析

相关资讯

C++策略模式:给行为装上可插拔引擎的工业实践 2026/10/12 22:50:27
maki和tree题解:树形DP与DFS统计恰好一个黑点的路径 2026/10/12 22:50:27
无监督工业缺陷检测新突破:HLGFA高低分辨率引导机制解析 2026/10/12 22:50:27

最新资讯

PX4飞控开发基础:从架构原理到仿真调试实战指南
Linux perf子系统详解:从事件采样到性能瓶颈定位
ClusterFuzz Bot 临时测试用例目录 fuzzer-testcases:占位目录设计与 tmpfs 内存化实践
Kener Heartbeat 心跳监控:为定时任务与内网服务打造推送式健康检查
Biopython 序列注释对象实战:SeqRecord、SeqFeature 与 Location 完整指南
mmaction2 音频动作识别实战:ResNet18 音频模型(TSNAudioHead)的完整训练与测试指南

今日推荐

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
CAN总线错误处理机制深度解析:从位错误到Bus Off的硬件级调试

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

JCache缓存过期策略:TTL与TTI的关键差异与实战解析

发布时间:2026/10/12 22:50:27
JCache缓存过期策略:TTL与TTI的关键差异与实战解析 1. 题面解析先把标准答案钉死最近又有人在群里贴出这道JCache基础篇的面试题JCacheJSR-107标准要求实现必须支持哪两种缓存过期策略题目很短答错率却很高。有人脱口而出“TTL和TTI”有人愣一下说“TTL和TTI不是一回事吗”还有人干脆把“LRU和LFU”搬出来。说实话能把LRU当过期策略来答说明对缓存两个核心机制——过期和淘汰——还没完全分清楚。今天就用实战视角把这道题彻底拆开从规范要求、接口设计、代码验证到面试追问一次说透。1.1 标准答案TTL 与 TTI正确答案是JCacheJSR-107要求实现必须支持两种基于时间的过期策略分别是TTLTime-To-Live存活时间和TTITime-To-Idle空闲时间。用JCache术语再翻译一遍就是“基于条目创建时间的过期策略”和“基于最后访问时间的过期策略”。TTL的计时起点是缓存条目被创建或者被更新的时刻。比如设置TTL为5分钟那么无论这5分钟内被读取多少次只要到了时间点条目就该被视为过期。TTI的计时起点是最后一次成功的访问操作每次读取都会让计时器归零重新开始。所以一条数据如果持续被高频读取TTI会被不断续期只有连续空闲超过设定时间它才会过期。这两种策略解决的是完全不同的缓存场景后面会详细展开。但先记住这个结论规范里“必须支持”这四个字非常重要它不是建议而是所有合格实现的最低门槛。1.2 很多人掉进去的坑过期策略不等于淘汰策略这道题最容易翻车的地方不是答不出TTL和TTI而是把过期策略和淘汰策略混为一谈。过期策略关心的是“这个条目什么时候失效”核心是时间维度。缓存框架会在读取时判断条目是否过期或者通过后台任务定期清理过期后再次get会得到null。淘汰策略关心的是“缓存空间满了我该先扔掉谁”核心是空间维度。LRULeast Recently Used淘汰最近最少使用的LFULeast Frequently Used淘汰访问频率最低的这些东西解决的是容量压力不是时间失效。一个典型场景用户会话数据设置30分钟TTI它不一定占满空间但超过30分钟不访问就该失效这是过期策略的活。如果缓存容量不够我们可以用LRU把不常用的会话先挤掉这是淘汰策略的活。两者可以共存但不能互相替代。面试官问出这道题就是想看你能不能清晰区分这两个维度。1.3 面试官问这道题到底想考什么表面上它是一道送分题背下来就行。但面试官真正考察的是三个层次第一你是否知道JCache是Java官方的缓存规范它把缓存API标准化到了javax.cache这个包里JSR-107是这个规范的编号。第二你是否知道规范对实现方有强制性要求而不是只给了一堆可选的API。第三你是否能把两种策略对应到实际的业务场景而不是停留在背概念。所以答题时最好三步走先给出标准答案再解释两种策略的计时规则最后各配一个典型使用场景。这样既展示了记忆准确又体现了理解深度。下面我们就进入规范背后的设计逻辑看看为什么偏偏是这两种策略被强制要求。2. 规范背后的设计逻辑为什么非要强制这两种JSR-107在制定时并不是拍脑袋定下“必须支持TTL和TTI”而是从大量缓存应用中抽象出来的最低共识。如果你看过Caffeine、Guava、Redis这些缓存方案的源代码或者配置项会发现它们都摆脱不了这两个基本的失效维度。JCache只是把最核心的那个交集抽成了规范。2.1 从业务场景反推TTL 和 TTI 各自的价值先看TTL。它的语义是“从写入开始计时到期就失效”。典型场景包括验证码、临时token、优惠券快照、配置推送。比如一个活动的商品价格页我们从上游接口拉取价格后缓存60秒60秒后必须重新拉取这就是TTL。这类数据的特征非常明显数据从出生开始就有固定的保鲜期不管用户看不看到期就得作废。再看TTI。它的语义是“从上一次被访问开始计时空闲超过阈值就失效”。典型场景是用户登录会话、表单填写状态、需要高频访问的热点数据。比如一个用户在电商网站浏览商品只要他不断点击会话就不该过期他离开半小时后再回来会话应该已经失效。TTI对这种“有活动就续命没活动就回收”的需求特别匹配。如果规范只要求支持一种实现方的自由度太大业务方一旦需要另一种就只能自己造轮子。如果要求支持太多比如把进程内缓存常用的弱引用、软引用也纳入强制那又会给分布式缓存实现造成巨大负担。所以JCache选择把TTL和TTI这两个普适性最强的时间策略定为硬性要求。2.2 ExpiryPolicy三个方法对应三种时机在JCache的javax.cache.expiry包里有一个核心接口ExpiryPolicy它定义了三个方法getExpiryForCreation、getExpiryForAccess、getExpiryForUpdate。getExpiryForCreation决定了条目刚创建时从哪个时刻开始算过期TTL主要落在这里。getExpiryForAccess决定了条目在每次读取命中后应该被赋予多长的续期TTI主要落在这里。getExpiryForUpdate决定了条目在更新值之后重新计算过期时间常见做法是把它当TTL重新开始计算。理解这三个方法比单纯背TTL和TTI更接近规范本意。TTL不是一个独立方法TTI也不是一个独立方法它们都是ExpiryPolicy在不同访问时机下返回Duration的具体体现。规范要求实现必须支持“基于创建时间的过期”和“基于最后访问时间的过期”落到代码层面就是默认提供的ExpiryPolicyBuilder必须能帮你构建出这两种策略并且底层缓存必须能正确处理访问时间戳的更新。2.3 Duration 的黑话ZERO、ETERNAL 和负数ExpiryPolicy的三个方法返回的类型是javax.cache.expiry.Duration不是java.time.Duration这一点很容易踩坑。JCache的Duration内部有两个常量ZERO表示立即过期ETERNAL表示永不过期。ZERO的使用非常微妙。如果你在自定义ExpiryPolicy的getExpiryForAccess里返回Duration.ZERO意味着条目在创建后第一次访问就会立即过期这通常不是你想要的效果。ETERNAL则是默认行为JCache默认情况下缓存条目是永不过期的除非你显式配置了过期策略。至于负数规范明确不允许返回负数。如果你传入一个负的Duration轻则被忽略重则抛异常。所以生产环境里如果有外部配置的动态过期时间一定先做钳制校验别把负数传给ExpiryPolicyBuilder。这个细节在面试追问里很常见也是实际开发中容易被忽略的坑。3. 代码实操亲手验证两种过期策略讲原理总归有点虚我们直接写代码验证。这里只使用JCache规范提供的接口具体实现只需要选择一个实现了JSR-107的开源库放到classpath里。为了避免被具体厂商牵着走我会尽量保持代码只依赖javax.cache的标准API。3.1 环境准备只依赖规范接口也能跑首先引入JCache API。如果你的项目是Maven结构依赖坐标一般是group为javax.cache、artifact为cache-api的jar这是官方规范接口。还需要一个具体的JCache实现类库每个实现的坐标不同这里就不展开写死你自己选一个主流的JSR-107实现即可。然后我们需要一段最基础的初始化代码获取CachingProvider和CacheManagerimport javax.cache.Cache; import javax.cache.CacheManager; import javax.cache.Caching; import javax.cache.configuration.MutableConfiguration; import javax.cache.expiry.Duration; import javax.cache.expiry.ExpiryPolicy; import javax.cache.expiry.ExpiryPolicyBuilder; import javax.cache.spi.CachingProvider; import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; CachingProvider provider Caching.getCachingProvider(); CacheManager cacheManager provider.getCacheManager(); MutableConfigurationString, String config new MutableConfiguration(); CacheString, String cache cacheManager.createCache(demo-cache, config);这里要提醒一点同一个CacheManager里创建同名缓存会抛异常。如果你反复调试要么换缓存名要么在创建前先判断缓存是否存在。另外CacheManager本身也是一个资源用完最好调用close()尤其在单元测试里频繁创建时不关闭可能会把内存堆满。3.2 配置 TTL5秒后必然消失下面创建一个TTL为5秒的缓存。我习惯用lambda给setExpiryPolicyFactory传参代码短意图也清楚MutableConfigurationString, String ttlConfig new MutableConfiguration(); ttlConfig.setExpiryPolicyFactory(() - ExpiryPolicyBuilder.timeToLiveExpiry(Duration.ofSeconds(5))); CacheString, String ttlCache cacheManager.createCache(ttl-demo, ttlConfig); ttlCache.put(orderId, 20250514001); System.out.println(ttlCache.get(orderId)); // 输出20250514001 TimeUnit.SECONDS.sleep(6); System.out.println(ttlCache.get(orderId)); // 输出null运行之后你会发现第一个get正常返回第二个get返回null。这就是TTL从put那一刻开始倒计时5秒期间不管你怎么get计时都不会重置。这里有一个值得注意的实现差异有些JCache实现是惰性过期也就是get时才检查时间戳有些实现会有后台线程定期清理。所以你在第5秒整去get结果可能是null也可能还没被物理删除但逻辑上一定不能返回旧值。规范要求的是“看起来过期”不强制物理删除时机。3.3 配置 TTI访问之后计时器重新归零TTI的验证稍微绕一点需要模拟“访问后重置”MutableConfigurationString, String ttiConfig new MutableConfiguration(); ttiConfig.setExpiryPolicyFactory(() - ExpiryPolicyBuilder.timeToIdleExpiry(Duration.ofSeconds(5))); CacheString, String ttiCache cacheManager.createCache(tti-demo, ttiConfig); ttiCache.put(sessionId, abc123); TimeUnit.SECONDS.sleep(3); System.out.println(ttiCache.get(sessionId)); // 输出abc123访问把过期时间续到了第8秒 TimeUnit.SECONDS.sleep(3); System.out.println(ttiCache.get(sessionId)); // 输出abc123距离最后一次访问只过去3秒未过期 TimeUnit.SECONDS.sleep(6); System.out.println(ttiCache.get(sessionId)); // 输出null最后访问后又空闲了6秒超过5秒阈值这里的逻辑可以这样理解第一次sleep 3秒时访问了一次空闲计时器被重置过期时间点从原来的第5秒推迟到了第8秒。第二次sleep 3秒后再次访问又重置到第11秒。最后一次连续空闲6秒超过5秒于是过期。TTI在分布式场景里有个经典坑多个节点同时访问同一个key每个节点都会更新自己的本地访问时间但如果没有统一的访问时间存储TTI就名存实亡。这一点面试官如果追问你可以补充“TTL比TTI更容易在分布式环境下实现”。3.4 组合策略和自定义防御式写法如果业务要求“最长TTL是10分钟但连续空闲3分钟就过期”JCache内置的builder没法直接组合。规范只强制分别支持TTL和TTI并不保证二者的组合语义。这时候你需要自己实现一个ExpiryPolicypublic class CombinedExpiryPolicy implements ExpiryPolicy { private final Duration ttl; private final Duration tti; public CombinedExpiryPolicy(Duration ttl, Duration tti) { this.ttl ttl; this.tti tti; } Override public Duration getExpiryForCreation() { return ttl; } Override public Duration getExpiryForAccess() { return tti; } Override public Duration getExpiryForUpdate() { return ttl; } }别急着把这段代码直接上生产这里有一个语义陷阱创建时返回ttl但第一次访问后getExpiryForAccess返回tti会把原有的ttl覆盖掉。也就是说如果创建后第5秒你访问了一次后续实际过期时间就变成了“这次访问后的3分钟”而不是原来剩余的ttl。真正要同时保证“最长不超过10分钟”和“空闲超过3分钟过期”必须在getExpiryForAccess里做计算取剩余TTL和新的TTI中更早的那个。这个计算逻辑实现难度不大但需要缓存框架能拿到“当前时间”和“已存活时间”JCache规范并没有统一暴露这些内部时间戳。所以更务实的做法是在业务代码里做好定时续期或过期判断不要过度依赖自定义组合策略。4. 常见问题与排查技巧实录这块内容来自我实际接手的几个项目问题很多坑不是配置错了而是大家对“过期”这个动作的理解有偏差。4.1 过期条目为什么不会立刻物理删除这是刚开始用JCache时最常见的困惑TTL设了5秒sleep6秒后一查内存缓存条目还在只是get不到了。原因很简单JCache规范只要求“get的时候必须返回null”并没有强制要求实现必须同步删除物理条目。有些实现采用惰性删除只有访问过期条目时才真正移除有些实现使用后台清理线程每隔一段时间扫描删除。从用户视角看条目逻辑上已经过期物理残留与否不影响正确性但会影响内存占用。如果你的缓存容器长期存在大量过期条目内存曲线会越来越难看的。这时可以去你的JCache实现里找有没有“定期清理”的配置项把清理间隔适当调小或者手动遍历缓存并触发get来加速失效。不过这类操作在分布式缓存上要小心遍历大缓存可能拖垮线上性能。4.2 为什么TTI缓存在高并发下看着像“永远不过期”这是TTI的天然特性只要每个请求都来访问这个key空闲计时器就一直在重置理论上这个key永远不会因为TTI过期。如果你的访问量非常高TTI缓存里的所谓“热点数据”可能会长时间不失效导致数据新鲜度变差。我之前遇到过一个问题团队用TTI缓存一套价格规则想着3分钟不访问就作废结果这套规则是全站每个请求都会读的TTI永远被续期价格规则几乎一整天都没有重新加载。最后改成TTL才解决问题。所以选型时要想清楚如果你希望数据必须定期重新拉取不管有没有人访问请用TTL。如果你只希望清理那些长时间没人用的数据用TTI。二者没有优劣只有适用场景不同。4.3 五大高频坑不是配置错了是理解错了现象根因解决建议设置了TTL但缓存一直有效用的可能是Caffeine的expireAfterWrite但JCache适配层没真正应用检查配置是否传到了底层Cache的expiryPolicyFactoryget之后TTI不重置实现可能只在写操作时更新时间戳查JCache实现文档确认是否支持读续期Duration传入负数JCache规范要求Duration非负配置来源处做Integer/Duration校验自定义ExpiryPolicy返回null方法返回值不能随意给null默认返回Duration.ETERNAL或显式指定同一CacheManager重复createCache同名缓存已存在会抛异常用cacheManager.getCache(name)判断或try-catch这些坑在单个实现里不一定会全遇到但它们都有共性不要拿“用过某个缓存框架”就直接替代对规范的理解。JCache是API标准底层实现各有各的实现策略面试官其实很爱从这个角度问下去。4.4 这种Error/Exception到底怎么排查有一次线上缓存突然大量失效日志里刷“expiry duration is not positive”一类的错误。排查后发现是我们一个外部配置项传了负数配置中心改了数据但代码没做钳制。后来我总结了一条排查路径第一复现时先看配置的Duration到底是多少第二在自定义ExpiryPolicy的三个方法入口加日志打印每次返回的Duration第三确认实现是否启用后台清理线程把清理日志打开第四区分缓存命中不到底是因为过期还是因为淘汰。这四条走完大部分问题都能定位。5. 面试答题的节奏与我的实战感受这道题虽然简单但答得好不好很能拉开差距。下面给出一个我验证过比较好用的答题结构以及一些面试之外的实际心得。5.1 一个可以直接复用的答题模板面试官问出“JCache标准要求实现必须支持哪两种缓存过期策略”时你可以这样回答第一层给结论必须支持TTL和TTI也就是基于创建时间的过期和基于最后访问时间的过期。第二层解释差异TTL从写入开始计时访问不会重置TTI从上次访问开始计时每次命中会续期。第三层落场景比如验证码用TTL用户会话用TTI。第四层补深度JCache里的ExpiryPolicy接口有三个方法分别对应创建、访问、更新三种时机TTL和TTI的本质是这三个方法在不同时机返回不同Duration。如果面试官追问“淘汰策略和过期策略的区别”你还可以顺带说一句LRU、LFU这类淘汰策略和TTL、TTI不冲突前者管容量后者管时效。这样整个回答就立体起来了。5.2 我在项目里踩过的TTI续期坑说一个我自己的真实经历。当时做一个活动页面需要把用户最近浏览过的商品列表缓存起来30分钟不活跃就清空。我当时很自然地选了TTI觉得非常合适。结果上线后发现一个问题只要用户在活动页挂着不动每5分钟会有一个前端心跳请求打过来把整个列表的TTI不断续期导致用户其实早就没看了但缓存里还是装着半小时前的浏览记录。后来我把心跳请求排除在TTI的访问更新之外或者用最后业务操作时间来判断才算解决了。这个案例说明TTI不是一个纯粹的“最后一次读取”概念在真实系统里什么是“有效访问”需要你自定义。5.3 学习建议从API到规范原文如果你正在准备Java面试我建议不要只背JCache的API可以找一下规范原文重点读关于ExpiryPolicy和CacheEntry的章节。那部分内容写得很清楚会告诉你默认策略是ETERNAL会告诉你实现必须支持哪些Duration语义也会告诉你访问操作如何影响过期时间。读完规范再去看具体实现源码你会发现很多“反直觉”的行为都有据可循。比如某些实现把TTL的检查放在get路径里某些实现用一个环形时间轮批量删除过期条目。这些细节面试官未必会问但你能聊出来就会让整个回答从“背题”变成“懂行”。我个人在实际排查缓存问题时最深的体会是与其对着具体缓存框架的配置项猜来猜去不如先把JCache这套最小语义吃透。TTL和TTI只是起点真正难的是理解规范方法背后的时机模型以及它在你当前技术栈里落地时的各种偏差。把这道题当成一次切入口顺带把ExpiryPolicy的源码和规范原文过一遍绝对不亏。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号