INFO COMMANDSTATS 可统计Redis各命令调用频次与平均耗时,重点关注cmdstat_get等读命令的calls和usec_per_call,结合定时采样差分识别实时热点;ZSET滚动窗口+TTL检测可定位“高访问且近过期”的脆弱热点。

用 INFO COMMANDSTATS 实时看谁在高频访问
Redis 本身不主动“通知”你某个 key 快过期了,但它会默默记录每个命令的调用频次和耗时——INFO COMMANDSTATS 就是这个账本。热点数据往往不是因为快过期才热,而是因为被高频读,所以先盯住“谁被查得最多”。
- 执行
redis-cli INFO COMMANDSTATS,重点关注cmdstat_get、cmdstat_hget等读命令的calls(调用次数)和usec_per_call(平均耗时) - 如果
calls高但usec_per_call也明显偏高,说明该 key 可能正面临大量并发读+缓存失效重载,属于“脆弱热点” - 注意:该命令只统计自 Redis 启动以来的累计值,需配合定时采样做差分(比如每分钟拉一次,算增量),否则看不出实时波动
用 ZSET + 时间窗口滚动统计访问频次
想感知“即将过期的热点”,本质是识别“正在被密集访问、且 TTL 剩余时间很短”的 key。Redis 没内置这种组合判断,得自己搭个轻量级监控层。
- 每次对目标 key(如
user:1001:profile)执行GET前,用ZADD hot_access_log 0 <code>user:1001:profile记录一次访问(score 用当前时间戳,便于后续按时间窗口筛选) - 另起一个后台任务,每 10 秒执行一次:
ZREMRANGEBYSCORE hot_access_log 0 (current_timestamp - 60)清理 1 分钟前的记录,再用ZCARD统计剩余数量 - 同时用
TTL user:1001:profile获取剩余过期时间;若ZCARD> 100 且TTLEXPIRE user:1001:profile 300) - 坑点:ZSET 的 score 是整数,
time.time()返回浮点数,直接塞进去会截断精度,建议统一用int(time.time())或毫秒级int(time.time() * 1000)
别依赖被动过期,主动查 TTL + 访问日志联动
很多人以为只要等 Redis 主动删 key 就行了,但实际中,“过期”和“被感知”之间有延迟——Redis 用的是惰性删除 + 定期抽样清理,TTL 返回 -2 表示 key 已逻辑删除但还没物理清理,这时你再去 GET 才知道没了。这中间的时间差,就是热点击穿的风险窗口。
- 关键动作不是等它过期,而是定期扫描你关心的 key 前缀,批量查
TTL,比如:redis-cli --scan --pattern "hot:*" | xargs -I {} redis-cli TTL {} - 把结果和你的访问日志(如 Nginx access log 或应用层埋点)做简单关联:如果某 key 近 5 分钟被请求 200 次,TTL 却只剩 12 秒,那它大概率会在下一波请求潮里崩一次
- 注意:全量 scan 对大实例压力不小,生产环境建议限制
COUNT参数(如--scan --pattern "hot:*" --count 100),分批轮询
逻辑过期 + 时间戳字段比单纯设 TTL 更可控
真正要“感知即将过期”,最稳妥的方式其实是绕开 Redis 的原生过期机制——自己在 value 里存一个过期时间戳,由业务代码决定何时刷新、何时拒绝服务。
- 写入时存成 JSON:
{"data": "xxx", "expire_at": 1741763400},而不是靠EXPIRE;读取后先解析expire_at,比当前时间早就走重建流程 - 好处是:你可以提前 30 秒触发预加载(比如
expire_at - 30就启动生成新缓存的异步任务),而原生 TTL 无法做到这种“预警式”控制 - 坑点:必须确保所有写入口都遵守该格式,且读逻辑统一校验;如果部分旧代码仍用
SETEX直接写字符串,就会导致结构不一致,解析失败
Redis 的过期机制是懒的,不是慢的。它不报错、不预警、不推送——所谓“感知”,永远是你在应用层用 TTL、访问频次、时间窗口三者交叉验证出来的结论。最容易被忽略的,是把“过期”当成一个瞬间事件,而实际上它是个持续数秒到数分钟的状态区间。










