@Cacheable无法直接设置动态TTL,因其过期时间由启动时固定的RedisCacheConfiguration.entryTtl决定,不支持运行时计算;unless、condition等属性不影响TTL,且注解本身无timeToLive等动态参数。

为什么 @Cacheable 无法直接设置动态 TTL
@Cacheable 注解本身不接受运行时计算的过期时间参数,它的缓存行为由 RedisCacheManager 统一管控。你写 @Cacheable(value = "user", key = "#id"),实际生效的 TTL 完全取决于该 cache 名对应的 RedisCacheConfiguration.entryTtl() 配置 —— 这个值在应用启动时就固化了,后续调用不会变。
常见错误现象包括:在方法里改了 entryTtl 属性但没重建 CacheManager;或误以为 unless、condition 能影响过期逻辑;甚至给 @Cacheable 加上自定义注解试图“拦截续期”,结果根本进不到缓存读写链路里。
根本原因在于 Spring Cache 是声明式抽象层,get() / put() 操作被封装在 RedisCache 内部,它只做一次性的 SET/SETEX,不触发 GETEX 或 PTTL+EXPIRE 组合。
用 RedisCacheManager 为不同 cache 设置固定但差异化的 TTL
这是最轻量、最稳定的方案,适合“按业务域划分缓存生命周期”的场景,比如 user 缓存 10 分钟、config 缓存 24 小时。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 不要复用默认的
RedisCacheManager,而是手动构建:用RedisCacheConfiguration为每个 cache 名单独配entryTtl - 配置方式推荐两种:
– 在 Java Config 中用Map<string rediscacheconfiguration></string>显式注册各 cache;
– 或使用 Spring Boot 3.2+ 的TtlRedisCacheManager(继承自RedisCacheManager),覆盖createRedisCache()方法,根据cacheName动态返回不同RedisCacheConfiguration - 注意:修改配置后必须重启应用才生效;热更新需配合
@RefreshScope+ 配置中心,但CacheManager本身不支持运行时刷新
想实现“读取即续期”,必须绕开 @Cacheable
Spring Cache 原生不支持访问时刷新 TTL,所谓“自动续期”只能自己动手。核心是放弃 @Cacheable,改用 RedisTemplate 手动控制读写流程。
关键点:
- 不能用
opsForValue().set(key, value, timeout, unit)—— 它等价于SETEX,会覆盖原有 key,丢失结构(比如你存的是HASH,它会把你整个干成STRING) - 续期必须原子:先
GET,再判断存在,再EXPIRE。否则并发读可能多次续期或漏续 - 推荐用 Lua 脚本保证原子性:
local val = redis.call("GET", KEYS[1])\nif val then\n redis.call("EXPIRE", KEYS[1], ARGV[1])\nend\nreturn val - 执行时传入 key 和新 TTL 秒数,返回原值 —— 这就是你要的“带续期的 get”
别踩这些坑:TTL 设置与 Redis 命令行为强相关
很多问题不是代码写错,而是对 Redis 底层命令语义理解偏差导致的。
-
SET key val EX 300和EXPIRE key 300效果一致,但前者只在 key 不存在时设过期;后者无论 key 是否存在都重置 TTL -
SET、GETSET、DEL会清除已有过期时间;而INCR、HSET、LPUSH不会 —— 所以如果你用opsForHash().put()更新一个已设 TTL 的 hash,TTL 依然有效 -
PERSIST命令会彻底移除过期时间,变成永久 key;线上误用可能导致内存持续增长 - Redis 的惰性删除机制意味着:key 过期后不访问就不会被删,
TTL返回-2表示 key 不存在,-1表示存在但没设 TTL —— 监控时得区分这两种状态
真正难的不是写几行代码续期,而是把 Key 当工程对象来设计:命名统一、TTL 有据可依、结构与命令匹配。否则就算加了 Lua 脚本,也会因为 key 类型混乱或覆盖误操作让续期失效。

















