spring boot需自建redis热点key探测与续期闭环:用actuator+meterregistry统一埋点归一化统计,禁用redis-cli--hotkeys和lfu;通过lua原子执行get+expire续期,并结合滑动窗口热度门限联动决策。

Spring Boot 本身不提供 Redis 热点 Key 自动探测能力,也**不会在读取时自动续期 TTL**;想实现“探测 + 续期”闭环,必须绕过 @Cacheable 和 RedisCacheManager.setExpires() 的默认行为,自己控制访问埋点、热度聚合、Key 判定与 TTL 刷新逻辑。
怎么用 Actuator + MeterRegistry 埋点采集 key 级访问频次
RedisTemplate 默认不记录每个 key 被访问了多少次,得在应用层主动计数。关键不是“在哪里埋”,而是“埋得是否可聚合、是否防爆炸”:
- 所有读操作(如
opsForValue().get()、opsForHash().hGetAll())应统一走封装好的RedisAccessService,避免分散在 controller 或多个 service 中 - metric name 推荐用
redis.key.access.{pattern}格式,比如把user:profile:12345归一化为user:profile:{id},否则 Prometheus 会因 label 过多而 OOM - 别在 AOP 里对每个方法调用都注册新 Counter——应复用同一个
Counter实例,仅通过 tag 区分 key 模式 - 启用
management.endpoints.web.exposure.include=health,metrics,prometheus,再用 Prometheus 抓取/actuator/prometheus
为什么不能靠 redis-cli --hotkeys 或 LFU 淘汰策略做实时探测
redis-cli --hotkeys 是采样命令,依赖 Redis 的 LFU 计数器,但它有硬伤:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- LFU 计数器只在每次访问时做概率性更新(非每次必增),低频 key 可能被误判为冷数据
- 它扫描的是整个 keyspace,key 数超千万时耗时数秒,无法支撑秒级热点识别
- LFU 不感知业务语义:比如
order:unpaid:{userId}高频但生命周期短,不应和product:info:1001同等对待 -
maxmemory-policy allkeys-lfu是内存满时才触发的被动淘汰,和“提前发现 + 主动续期”完全无关
如何用 Lua 脚本实现“读取即续期”且不破坏原子性
单纯先 GET 再 EXPIRE 有竞态风险:key 可能在两次命令之间被删掉。必须用 Lua 保证 GET + EXPIRE 原子执行:
local val = redis.call('GET', KEYS[1])
if val then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return val
调用方式:
- 用
redisTemplate.execute(newDefaultRedisScript(script, String.class), Collections.singletonList(key), String.valueOf(newTtlSeconds)) - 注意:
newTtlSeconds应大于原 TTL,否则可能刚续就过期;建议设为原值的 1.5 倍或固定值(如 30 分钟) - 该脚本只对已存在的 key 续期,不存在时返回 nil —— 此时业务需触发重建,不能跳过空值检查
- 别用
GETSET或SETEX替代,它们会覆盖值或强制改结构(比如把 hash 当 string 写)
怎样联动热度判定与 TTL 续期决策
光续期所有 key 是浪费资源,得加一层“热度门限”。推荐用滑动窗口 + 本地缓存做轻量判断:
- 用
ConcurrentHashMap<string longadder></string>存每分钟每个 key pattern 的访问次数,窗口滚动清理 - 当某 pattern(如
product:detail:{id})在最近 2 分钟内累计超 5000 次,标记为潜在热点 - 对该 pattern 下的真实 key(如
product:detail:1001)启用 Lua 续期,并写入 Redis 有序集合hot_keys,score 为最后更新时间 - 后台线程定期扫
hot_keys,对 score 超 10 分钟未更新的 key 清除标记——防止误判滞留 - 特别注意:续期动作本身也会被计入访问频次,需在埋点逻辑里排除 Lua 调用路径,否则形成自激循环










