不能靠@cacheable注解直接加随机过期时间,因其不支持per-key动态ttl;需用redistemplate手动set+expire实现真随机,或自定义rediscachewriter兼容@cacheable。

不能靠 @Cacheable 注解直接加随机过期时间——它压根不支持 per-key 动态 TTL,硬写会失效或被忽略。
为什么 @Cacheable 配 entryTtl 会引发雪崩
显式配置 RedisCacheConfiguration.entryTtl(Duration.ofMinutes(30)) 后,所有 key 都从写入时刻起严格倒计时 30 分钟。一旦批量预热(比如启动时刷 10 万个商品缓存),这些 key 就会在同一秒全部过期。Redis 自身的惰性删除机制挡不住这波穿透请求,数据库瞬间被打满。
常见错误现象:redis_expired_keys_total 指标突增、DB Threads_running 拉满、连接池 wait timeout 飙升。
- 别信
lazyfree-lazy-expire yes能防雪崩——它只异步删 key,不阻止请求穿透 - @Cacheable 的
unless、condition或自定义KeyGenerator都无法干预 TTL 计算 - 全局
entryTtl是“静态保险丝”,不是“动态分流阀”
用 RedisTemplate 手动 set + expire 实现真随机
必须绕过 Spring Cache 抽象层,在业务逻辑中显式控制每个 key 的过期时间。核心是:每次写入都独立生成随机偏移,且避免负数 TTL。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 基础 TTL 设为 3600 秒时,推荐偏移范围 ±360 秒(即 10%),调用
ThreadLocalRandom.current().nextInt(-360, 360) - 禁止在方法内 new
Random(),毫秒级并发下种子重复,导致大量 key 拿到相同随机值 - 别用
opsForValue().set(key, value, duration)—— 它底层走SETEX,覆盖已有 key 且无法复用原值 - 正确姿势是两步:
redisTemplate.opsForValue().set(key, value)+redisTemplate.expire(key, actualSeconds, TimeUnit.SECONDS) - 务必校验
actualSeconds > 0,避免expire(key, -10, SECONDS)导致立即过期
封装工具类强制统一逻辑,禁用裸写 Random
把随机计算逻辑收口,避免各处散落 new Random().nextInt()。这是最容易被忽视但最致命的一环。
- 工具方法签名建议为:
setWithJitter(String key, Object value, long baseSeconds, long jitterRangeSeconds) - 内部用
ThreadLocalRandom.current().nextLong(-jitterRangeSeconds, jitterRangeSeconds)生成偏移 - 返回值检查
redisTemplate.expire()是否为true,false表示 key 不存在,需排查 set 是否失败 - 如果 value 是对象,确认
redisTemplate.getValueSerializer()已设为GenericJackson2JsonRedisSerializer,否则读出来是乱码 - 批量写入时,必须在循环体内每次调用随机函数——绝不能在 for 外只算一次 jitter 然后复用
自定义 RedisCacheWriter 是唯一兼容 @Cacheable 的方案
若项目已重度依赖 @Cacheable(cacheNames = "users", key = "#id"),又不想改业务代码,只能动底层写入器。但它对所有 cacheName 生效,无法按业务区分策略。
- 继承
DefaultRedisCacheWriter,重写put(...)方法,在调用父类前对传入的duration做扰动 - 在
RedisCacheConfiguration中通过cacheWriter(new RandomizedCacheWriter(delegateWriter))注入 - 注意:必须显式配置
cacheDefaults(...),否则新 cache 实例 fallback 到全局默认,随机逻辑不生效 - 该方式无法识别方法级注解参数(如想给某个
@Cacheable单独配 ±60 秒),此时需上 AOP + 自定义@CacheTTL注解
真正难的不是生成随机数,而是让每个 key 的过期时间分布符合业务访问节奏——热点数据不能因随机偏移过早淘汰,冷数据也不该因偏移过大长期滞留。偏移范围必须和基础 TTL 成比例,且要持续监控 cache.getIfPresent() 命中率,跌到 85% 以下就得调参。










