不能直接用INFO stats的keyspace_hits与misses相除计算实时命中率,必须通过时间窗口内增量(如30秒)计算Δhits/(Δhits+Δmisses),因其为累计值,反映的是历史平均而非当前负载表现。

直接用 INFO stats 看不到实时命中率
很多人一上来就执行 redis-cli INFO stats | grep keyspace_,拿 keyspace_hits 和 keyspace_misses 直接相除,算出来的是 Redis 启动以来的平均值,不是当前负载下的真实表现。比如实例跑了三个月,前两个月流量低、命中高,第三个月突发冷请求,这个“历史平均”会掩盖问题。真正要判断“现在缓存是否健康”,必须抓两个时间点的差值。
ThinkPHP 里怎么安全采集 Redis 增量命中率
不能在每次 HTTP 请求里都调 INFO stats——开销大、干扰监控精度、还可能被限流。推荐做法是:单独起一个轻量后台任务(如 Laravel 的 schedule 或 ThinkPHP 的命令行任务),每 30 秒执行一次采集,把 Δhits / (Δhits + Δmisses) 存到本地日志或 Prometheus。
- 用
php think redis:hit-rate这类自定义命令封装采集逻辑,避免硬编码连接参数 - 采集前先做一次
KEYS *检查是否误启了全量扫描(该命令在生产环境应禁用) - 注意
keyspace_hits不统计EXISTS、SCAN、读已过期 key 的行为,如果你的 ThinkPHP 业务大量用Cache::has(),这部分“逻辑命中”不会反映在指标里
命中率持续低于 0.85,优先排查 ThinkPHP 缓存配置陷阱
ThinkPHP 默认开启缓存前缀(prefix)、自动序列化(serialize)、以及 cache(true, 3600) 这类写法隐式使用默认驱动。这些看似无害的配置,叠加后容易导致键膨胀或类型不一致:
-
prefix和业务代码里手动拼的前缀重复,比如配置写了'prefix' => 'tp6_',又在Cache::get('tp6_user_123')里再加一遍,造成 key 冗余、击穿概率上升 -
serialize开启时,不同 PHP 版本或扩展(如 igbinary)反序列化失败,Redis 返回nil,记为miss,但错误静默,不易察觉 - 数据库查询缓存(
Db::cache())和手动缓存(Cache::get())混用,同一份数据存两套 key,更新不一致,引发脏读和无效 miss
Redis 7.0+ 的 hit_rate 字段别当真
这个字段在 INFO stats 输出里看起来很省事,但它依赖两个前提:keyspace_misses > 0 且总请求数大于 0。刚启动、或所有请求都命中的实例,它压根不显示;更麻烦的是,它只计算最近一次采样窗口,没做平滑处理,抖动极大。线上观察过多次,hit_rate: 0.98 和下一行 keyspace_hits: 123456789 对不上——说明它不是简单除法,而是带内部状态的估算值。信它不如自己算 Δ。
立即学习“PHP免费学习笔记(深入)”;
真正关键的不是单点数字,而是连续几个窗口的下降趋势:如果 30 秒命中率从 0.92 → 0.87 → 0.81,就要立刻看慢日志和 commandstats,而不是等告警触发。



















