Redis 缓存命中率从 99% 掉到 60%:4 个成因与 5 条排查命令

发布时间:2026/10/15 3:58:08

Redis 缓存命中率从 99% 掉到 60%:4 个成因与 5 条排查命令 我不会起名字322· 后端 / 算法 / 数据库 技术栈 力扣 Hot100 Go 项目 Redis MySQL文章目录Redis 缓存命中率从 99% 掉到 60%4 个成因与 5 条排查命令一、先别信监控命中率要用增量算二、4 个成因成因 1key 集中过期TTL 撞车成因 2大量冷 key 写入假性命中率下降成因 3热数据被淘汰掉了成因 4穿透查了根本不存在的 key三、5 条排查命令四、配置模板五、4 步排查顺序六、总结Redis 缓存命中率从 99% 掉到 60%4 个成因与 5 条排查命令监控上缓存命中率一夜之间从 99% 掉到 60%紧接着数据库 QPS 涨了三倍慢查询开始报警。Redis 本身没报错内存也没满连接数正常——只是缓存突然不中用了。命中率是缓存系统里最灵敏的一个指标它掉 40 个点意味着每 10 次查询里有 4 次直接打到数据库上。这篇文章先讲清命中率怎么算才准很多监控算错了导致你看到的数字本身就不可信再拆 4 个成因和对应的 5 条排查命令。一、先别信监控命中率要用增量算INFO stats里有两个计数器redis-cli INFO stats|grepkeyspace# keyspace_hits:18273645# keyspace_misses:184321很多人直接拿这两个数算hits / (hits misses)。问题是这两个是从 Redis 启动以来的累计值——你的实例跑了 180 天累计了 1800 万次命中那么今天发生什么都会被这个巨大的分母稀释掉曲线会平得毫无意义。正确的算法是增量命中率hit_rate Δhits / (Δhits Δmisses) 其中 Δ 本次采样值 - 上次采样值间隔 30s 或 1min用命令行快速算间隔 60 秒采两次# 第一次redis-cli INFO stats|grep-Ekeyspace_hits|keyspace_misses/tmp/a.txtsleep60# 第二次redis-cli INFO stats|grep-Ekeyspace_hits|keyspace_misses/tmp/b.txtawk-F: NRFNR { h1$2; next } /keyspace_hits/ { h2$2 } /keyspace_misses/ { m1$2; getline; } /tmp/a.txt /tmp/b.txt2/dev/null# 更简单的方式直接看 1 分钟内的变化量redis-cli--stat# ⭐ 它打印的就是每秒/每分钟的增量含 keyspace 变化redis-cli --stat是最省事的工具它输出的就是增量第二列开始就有keys、hits、misses的实时变化。二、4 个成因成因 1key 集中过期TTL 撞车最典型的一类批量任务在某一刻写入了几十万个 key并且给了完全相同的 TTL。这些 key 会在同一秒集体失效那一瞬间所有请求全部穿透到数据库。验证方法看 key 的过期分布redis-cli INFO keyspace# db0:keys1523846,expires1520012,avg_ttl86392000expires / keys接近 1 说明几乎所有 key 都有 TTL如果avg_ttl也很规整就要警惕集中过期。解决TTL 加随机抖动把过期时间打散。// Go 示例基础 TTL 随机抖动避免同一批 key 同时失效functtlWithJitter(base time.Duration,jitter time.Duration)time.Duration{ifjitter0{returnbase}returnbasetime.Duration(rand.Int63n(int64(jitter)))}// 用法基础 30 分钟抖动 0~10 分钟ttl:ttlWithJitter(30*time.Minute,10*time.Minute)rdb.Set(ctx,key,val,ttl)不方便的场景key 由别的服务写入至少在批量任务里分批设置不同的 TTL。成因 2大量冷 key 写入假性命中率下降命中率的分母里包含了所有查询如果突然涌入一大批「从来没查过的新 key」那第一次查必然 miss。这是正常的冷启动不是故障。三种常见来源新业务/新 key 前缀上线新接口的 key 是全新的实例重启或主从切换内存里的 key 全没了等于回到冷启动批量写入把缓存灌满数据预热任务写了大量 key但真正被查询的只是其中一小部分。判断依据看keyspace里的keys总数是不是突然变大以及命中率下降的时间点是不是与某个发布/重启对齐redis-cli INFO keyspace# 关注 keys 的增长曲线redis-cli DBSIZE这一类的正确处理是「等它预热」「提前预热」而不是调参数。成因 3热数据被淘汰掉了maxmemory到顶之后Redis 会按maxmemory-policy淘汰 key。如果策略选错被淘汰的可能是最热的那一批redis-cli INFO stats|grepevicted_keys# 淘汰计数持续上涨说明一直在淘汰redis-cli CONFIG GET maxmemory-policy策略行为适用场景noeviction不淘汰直接拒写数据不能丢缓存场景别用allkeys-lru所有 key 里淘汰最久未用的纯缓存最常用volatile-lru只淘汰设了 TTL的 key 里最久未用的有持久数据 缓存混存allkeys-lfu淘汰访问频率最低的4.0⭐ 有明显冷热分化时比 LRU 更准volatile-lfu只在有 TTL 的 key 里按频率淘汰混存 冷热分化allkeys-random随机淘汰基本不用另一个隐蔽的杀手是bigkey一个几 MB 的 key 会占掉大量空间让maxmemory更快触顶间接导致热数据被挤走。redis-cli--bigkeys# 找每种类型最大的 keyredis-cli MEMORY USAGEbigkey# 精确核算单个 key成因 4穿透查了根本不存在的 key如果业务在查一批数据库里也没有的数据比如被刷接口、爬虫遍历不存在的 ID、或者上游传了非法参数那么每次查询必然 miss而且永远不会被缓存命中。特征很明显miss 的增长量与请求量严格成正比且 key 总量不增长因为这些 key 压根没写进去。三种处理手段空值缓存查不到时也写一个短 TTL 的空标记注意别把空值 TTL 设太长会导致真数据写入后短时间读不到布隆过滤器写入时同步更新布隆过滤器查询前先判存在性注意布隆过滤器有误判率且删除支持有限参数校验在业务层挡掉明显非法的请求这是最便宜的一招。三、5 条排查命令#命令看什么说明1redis-cli INFO stats | grep keyspacekeyspace_hits/keyspace_misses⭐ 必须算增量累计值没有意义2redis-cli --stat实时增量最直接含 keys/hits/misses 每秒变化3redis-cli INFO keyspacekeys/expires/avg_ttl判断集中过期、冷启动、key 总量变化4redis-cli --hotkeys热 key 排行⭐ 需要maxmemory-policy是allkeys-lfu或volatile-lfu5redis-cli --bigkeysMEMORY USAGE key大 key判断是不是 bigkey 挤占了空间补充两条按需使用的redis-cli OBJECT FREQkey# 查看某个 key 的访问频率计数LFU 策略下redis-cli SLOWLOG GET10# 大 key / 复杂命令常常伴随慢查询redis-cli INFO memory|grep-E^used_memory:|^maxmemory:# 是否触顶--hotkeys需要 LFU 策略才准确。如果你的实例是 LRU 策略又不想改OBJECT FREQ一样看不了——这种时候可以临时改用MONITOR只能短时间短命令会显著影响性能生产慎用timeout10redis-cli MONITOR|awk{print $4}|sort|uniq-c|sort-rn|head-20# 统计 10 秒内被访问最多的命令/key⚠ 高 QPS 实例上不要用四、配置模板# 淘汰策略有明显冷热分化时用 LFU maxmemory 20gb maxmemory-policy allkeys-lfu # 混存场景有不能丢的持久数据用 volatile-lfu并确保缓存 key 都设了 TTL # LFU 的衰减与计数器可选微调 lfu-log-factor 10 # 越大频率区分度越高默认 10 lfu-decay-time 1 # 频率计数衰减周期分钟默认 1 # 避免集中过期TTL 一定要带抖动 # 这条靠代码保证配置层面只能做到监控 TTL 分布Go 侧的空值缓存与穿透防护可直接抄packagemainimport(contexterrorstimegithub.com/redis/go-redis/v9)varErrNotFounderrors.New(not found)// getWithCache 带空值缓存的读取避免同一个不存在的 key 反复穿透funcgetWithCache(ctx context.Context,rdb*redis.Client,keystring,ttl,emptyTTL time.Duration,loadfunc(context.Context,string)(string,error),)(string,bool,error){val,err:rdb.Get(ctx,key).Result()iferrnil{ifval__NULL__{// 空值标记说明数据库里确实没有return,false,ErrNotFound}returnval,true,nil}if!errors.Is(err,redis.Nil){return,false,err// 真故障不要当成 miss}// 缓存没有 → 回源real,err:load(ctx,key)iferr!nil{return,false,err}ifreal{// ⭐ 空值也写缓存TTL 要短避免真数据写入后读不到_rdb.Set(ctx,key,__NULL__,emptyTTL).Err()return,false,ErrNotFound}// ⭐ TTL 带抖动避免同批 key 同时失效jitter:time.Duration(int64(ttl)/10)_rdb.Set(ctx,key,real,ttljitter).Err()returnreal,true,nil}五、4 步排查顺序第 1 步确认命中率的算法对不对。先用redis-cli --stat看实时增量确认命中率是真掉了还是监控算错了累计口径是最常见的假警报。第 2 步看 key 总量和过期分布先排除冷启动和集中过期。INFO keyspacekeys是不是突然涨了冷启动/新业务、expires占比是不是接近 100% 且avg_ttl很规整集中过期。这两类占了实际案例的一大半。第 3 步看有没有在淘汰。INFO stats | grep evicted_keys持续上涨 used_memory接近maxmemory→ 热数据被挤走了。接着用--bigkeys找占空间的大 key并重新评估maxmemory-policy冷热分化明显就用 LFU。第 4 步排除穿透。如果 miss 与请求量严格成正比、key 总量不涨、且上面三步都正常 → 大概率是穿透。按key维度统计访问确认是不是有一批根本不存在的 ID 在被反复查询然后上空值缓存或布隆过滤器。六、总结命中率要用增量算Δhits / (Δhits Δmisses)或直接用redis-cli --stat。用累计值算出来的曲线会被历史稀释导致真出事时看不出来。4 个成因① key 集中过期TTL 撞车加随机抖动② 大量冷 key 写入冷启动属正常现象靠预热解决③ 热数据被淘汰maxmemory 触顶 策略选错 bigkey 挤占④ 穿透查不存在的数据空值缓存 / 布隆 / 参数校验。5 条命令INFO stats增量、--stat实时、INFO keyspacekey 与 TTL 分布、--hotkeys热 key需 LFU、--bigkeysMEMORY USAGE大 key。MONITOR只能短时用高 QPS 实例上别碰。排查顺序先确认算法 → 再看 key 总量/TTL 分布排除冷启动与集中过期→ 然后查淘汰与 bigkey → 最后才怀疑穿透。这个顺序能覆盖绝大多数真实案例。命中率要和数据库 QPS 一起看命中率掉 数据库压力涨两者是同一个信号的两面告警应该成对配置而不是只盯一个。本文命令适用于 Redis 6/7--hotkeys需要 LFU 类淘汰策略OBJECT FREQ同理。5.x 及更早版本没有--memkeys--hotkeys的输出也依赖策略配置。
延伸阅读

更多相关文章

2026/10/15 3:58:08

Python Flask 总结备忘:从入门到精通的完整路线

本文速览(TL;DR) 这是一份面向「总结备忘」与「进阶精通」双目标的 Flask 完整笔记。全文按「基础 → 原理 → 工程化 → 性能 → 安全 → 部署」的路径展开:先搭建环境与项目结构,再掌握路由、视图、Jinja2 模板与请求/响应生命周…

2026/10/15 3:58:08

Python毕设怎么做?基于Hadoop的新生儿败血症临床多指标数据可视化分析系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习

计算机毕设指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流!也可以在主页上或文末下与…

2026/10/15 3:58:08

HC-SR04测距真相:为何必须用输入捕获+从模式复位

title: HC-SR04 超声测距为什么必须用「输入捕获 从模式复位」?把 Echo 的每一段路都走一遍 tags: [STM32, 定时器, 输入捕获, HC-SR04, 超声测距, 从模式控制器] ---# 前言初学 HC-SR04,最容易卡住的不是接线,而是三个问题:1. *…

2026/10/15 5:18:12

河北工业大学Java期末复习题:25道选择题考点剖析

简介:河北工业大学Java期末考试复习题(部分答案)是一份面向该校Java课程本科生的期末备考资料,适用于考前集中复习、常见题型演练和查漏补缺,也可供任课教师出题或备课参考。资源包共包含1个doc文档,整体约…

2026/10/15 5:18:12

FileChannel深度解析:零拷贝与内存映射实战指南

在Java的NIO体系里,FileChannel是很多人学完Buffer之后第一个真正上手的通道类。它解决的问题很朴素:让文件的读写不再像传统IO那样一边读一边频繁拷贝,而是把文件抽象成一个可定位、可映射、可高效搬移的通道。作为整个NIO中唯一和本地文件系…

2026/10/15 5:18:12

分数阶Calderón问题贝叶斯反演:MCMC与MATLAB实现全解析

做反问题这些年,我发现一个特别有意思的共识:只要把经典算子换成分数阶,问题难度往往会跳一个台阶,但能解释的物理现象也瞬间丰富很多。分数阶Caldern问题就是这个方向的典型代表——它既要处理边界测量反演内部参数的经典难点&am…

2026/10/15 5:18:12

LLM推理性能测试指南:并发压测与指标归因实战

性能测试这件事,最怕的不是结果不好看,而是结果说不出为什么,换个环境就崩。上个月我把 vLLM 和 SGLang 两个推理引擎拉到一起做并发实验,前前后后折腾了两周,最后发现坑全不在引擎本身,而在实验设计和指标…

2026/10/15 5:18:12

一文搞懂分类问题:算法选型、评估指标与实战避坑

1. 分类问题到底在解决什么:先想清楚边界在哪很多人一上来就背模型:逻辑回归、决策树、SVM、随机森林……但真到项目里,第一个卡住的往往不是模型不会调,而是"这个问题到底该不该当成分类来做"。我见过不少同学把回归问…

2026/10/15 5:13:12

PostgreSQL生产库备份方案:Barman全量备份与WAL归档实战

很多DBA做PostgreSQL备份,第一反应就是写个 cron 脚本,定时调 pg_dump。这套逻辑在几十 GB 的小库上确实够用,但当你手里管着几百 GB 甚至上 TB 的生产库,还要回答“我能恢复到什么时间点”“备份到底能不能用”这两个问题时&…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/15 0:02:22

防关联浏览器哪个好?三款产品实测差异解析

新手做多店铺,最纠结的就是防关联浏览器选哪个。飞跨、网易、站斧这三款被问得最多,先给结论:要一体化省心、长期便宜,选站斧;要网易招牌和稳,选网易;预算紧做东南亚 Lazada,选飞跨。…

2026/10/15 0:02:22

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成 一、技术背景 大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑