缓存穿透的成因与四层防护:从缓存空值到布隆过滤器

发布时间:2026/9/9 16:49:55

缓存穿透的成因与四层防护:从缓存空值到布隆过滤器 缓存穿透这个问题只要做过高并发接口的同学基本都会碰到。它的核心现象是请求查询的数据在缓存里没有在数据库里也没有于是每一次请求都会绕过缓存直接打到数据库。正常用户偶尔查一个不存在的 ID 问题不大真正危险的是恶意脚本专门挑不存在的 key 轮番轰炸数据库 QPS 瞬间被拉满缓存层形同虚设。很多教程遇到穿透就推荐“缓存空值”也就是查询为空时也往 Redis 里写一个空结果。这个手段不是不能用但它只能算入门级应急处理。如果你把防穿透的全部希望押在缓存空值上生产环境迟早要出问题。下面把穿透的成因、缓存空值的硬伤以及真正适合生产的分层防护方案一起拆开讲。1. 先分清缓存穿透、缓存击穿、缓存雪崩不是一回事很多入行两三年的同学遇到缓存问题第一反应就是“加缓存、调 TTL”但穿透、击穿、雪崩这三类问题成因完全不同用错方案等于白做。排查之前先把三者的本质区别讲清楚。1.1 穿透查的数据根本不存在缓存穿透指的是查询一个缓存和数据库里都不存在的数据。举个例子。订单系统里用户查询订单详情正常订单肯定存在但攻击者可以循环请求 order_id -1、0、99999999 这样不存在的 ID。缓存里没有这类 key数据库里也没有对应记录所以每次查询都是缓存 miss然后去数据库执行一次无结果查询。单次无结果查询本身很轻但如果请求量是每秒几千次数据库连接池、SQL 执行、日志输出都会被拖垮。穿透的关键特征是“数据不存在”。这一点决定了它和另外两个问题有本质区别。很多人把穿透和击穿混为一谈是因为两者表象都是“缓存没命中”但穿透是查不到东西击穿是东西存在但缓存刚好过期。1.2 击穿和雪崩数据存在只是缓存没了缓存击穿是某一个热点 key 突然过期大量并发请求同一时刻查同一个 key缓存 miss 后全部涌向数据库。数据本身是存在的只是缓存刚好失效所以数据库那边能查到结果回填缓存后压力就会降下来。缓存雪崩是大量 key 在同一时间过期或者缓存节点整体不可用导致大量请求同时落到数据库。数据和 key 本身没问题问题出在过期时间设置不分散或者缓存服务本身挂了。判断方法很简单数据库里到底有没有这条数据。有就是击穿或雪崩没有才是穿透。这个判断是后续所有方案选择的前提判断错了方案必然错。1.3 为什么穿透最难提前发现击穿和雪崩有相对明显的预警信号。热点 key 即将过期、批量 key 设置了相同 TTL、缓存节点状态异常这些都能在监控里看到。穿透不一样它在缓存里看不到任何痕迹因为不存在的数据本来就不会进入缓存你只能从数据库这一侧的“高 QPS 但低命中结果”去反推。更麻烦的是穿透不仅可能是攻击。业务上的脏数据、前端传参错误、数据同步延迟都会造成大量不存在 key 的查询。如果一上来就按攻击处理容易误伤正常请求如果不管数据库压力又降不下来。这就是为什么需要分层防护而不是靠某一个技巧硬撑。2. 缓存空值方案能做应急扛不住真实攻击缓存空值的思路很直观数据库查询结果为空时也把 key 写进缓存value 记为一个标记空值并设置一个较短的 TTL。这样短时间内同一个不存在 key 的重复请求就会命中缓存不再穿透到数据库。2.1 基本实现查询为空时也写缓存用 Redis 的伪代码表示核心逻辑大致是这样key order:detail: orderId value redis.get(key) if value ! null: if value EMPTY_MARK: return null return parse(value) row db.query(orderId) if row null: redis.set(key, EMPTY_MARK, ttl 60s) return null redis.set(key, serialize(row), ttl 30min) return row这段逻辑看起来没问题小流量、少量不存在 key 的场景下也确实能降低数据库压力。但它有三个硬伤决定它只能当应急手段不能当完整方案。2.2 第一个坑缓存空值会把内存吃成“垃圾场”缓存空值只对“同一个 key 被反复查询”有效。攻击者如果不用同一个 key而是随机拼接一批不存在的 ID比如 100 万个不同的非法 ID那么每次请求都会写入一个新的空值缓存。假设每个 key 带标记和过期时间大约占用 100 字节100 万个 key 就是 100MB1000 万个就是 1GB。这些空值既没有业务价值又占着 Redis 内存还得靠淘汰策略慢慢清掉。更糟糕的是如果攻击持续制造新 keyRedis 内存会持续增长最终把正常业务数据的缓存挤出去。换句话说这个方案在“少量 key 高频查询”时有效在“大量 key 低频查询”时反而会放大问题。攻击者只要不断换新 ID就能让你的缓存内存持续失血。2.3 第二个坑TTL 一到攻击照样穿透空值缓存必须设置短 TTL。因为数据可能在未来被写入TTL 太长会导致新数据写入后用户仍然在短期内读到空值造成数据不一致。但 TTL 一短问题也来了。攻击者完全可以等待 TTL 过期后对同一批 key 再发起一轮请求。每过 60 秒就能穿透一次数据库依然会被周期性打穿。TTL 设置在 5 分钟数据库就每 5 分钟承受一次峰值TTL 设置在 1 分钟数据库就每 1 分钟承受一次。你并没有消除穿透只是把穿透频次从“每秒一次”降到了“每分钟一次”。2.4 第三个坑空值无法区分“不存在”和“暂时查不到”缓存空值会把所有返空的结果都标记成同一个语义这个 key 不存在。但生产环境里查询为空的原因非常复杂数据真的不存在。数据已经入库但主从同步延迟查询从库没查到。接口权限或租户隔离导致当前用户看不到。下游服务超时返回了空集合。如果这些场景都统一写成空值缓存就会出现短时间内的数据不一致。用户刚创建了一条数据却因为之前查询过一次空值被缓存在 TTL 内一直看不到新数据。这种问题在订单、库存、支付场景里是不可接受的。所以我的判断是缓存空值可以用但它只是兜底手段不能作为第一防线更不能作为唯一方案。3. 分层防护才是正解四层防线的落地顺序真正适合生产环境的防穿透方案应该是多层组合每一层解决一类问题而不是把压力全部交给某一层。下面按落地顺序讲。3.1 第一层接口参数校验把明显非法的请求挡在外面很多穿透请求在入口就能被拦住。订单 ID 是否为正整数、长度是否合法、用户是否有权限查询该资源、分页参数是否越界这些校验不需要动用缓存和数据库。参数校验看似简单实际是最容易被忽略的一层。很多团队图省事把所有参数直接交给业务逻辑处理结果恶意请求轻松穿过接口层打到数据库。这一层的目的不是防御高级攻击而是过滤掉那些“明显不该查”的请求。如果接口本身需要登录还要考虑用户维度和风控维度同一用户短时间内的异常查询频率、IP 维度的请求频率都可以在网关或服务层做限流。不要觉得“这点请求量无所谓”攻击流量往往是正常流量的几十倍。3.2 第二层布隆过滤器拦截绝大多数不存在 key布隆过滤器是一个空间效率极高的概率型数据结构用来判断“某个 key 是否可能存在”。它的特性是判断“不存在”是确定的判断“存在”可能有误判。也就是说如果布隆过滤器说某个 key 不存在那它一定不存在如果说存在则可能是误判实际并不存在。用法是在系统启动时把所有合法 ID 加载进布隆过滤器。请求进来后先查过滤器if not bloomFilter.contains(orderId): return null # 这个 ID 肯定不存在直接返回不走缓存和数据库这样绝大多数不存在的 key 在进入缓存之前就被拦截了。布隆过滤器本身非常省内存几亿个 key 也只需要几百 MB而且查询速度是 O(1)。实现时有几个细节要注意初始化时要确认数据量级选择合理的位数组大小和哈希函数个数误判率通常设置在 0.1% 到 1% 之间。每次新增合法 ID 时要同步往布隆过滤器里添加。删除 ID 时不需要处理因为布隆过滤器不支持删除误判多几个空值请求是可以接受的。如果业务数据量变化大可以考虑定时重建过滤器而不是在线上动态调整。注意布隆过滤器只能判断“可能存在”。它说存在时仍然要走到缓存和数据库不能直接当成“一定存在”返回结果。关于误判多说一句误判本身不致命因为误判的 key 走到缓存和数据库时依然查不到数据最多是浪费一次查询。真正的问题是不应该把所有不存在 key 都放过去布隆过滤器已经把 99% 以上的不存在 key 挡掉了剩下的误判数量级已经非常低。3.3 第三层短 TTL 空值兜底处理布隆过滤器的误判布隆过滤器有误判所以还需要一层兜底。这一层才轮到缓存空值。误判率如果是 1%攻击者每发 100 个不存在的 key依然有 1 个会穿透到数据库。这时候用短 TTL 的空值缓存把这 1% 的 key 在短时间内缓存起来数据库压力就能被控制住。注意这一层的定位和文章开头说的“初学者方案”完全不同这里缓存空值不是主力而是给主力防线打补丁。TTL 可以设置得比较短比如 30 到 60 秒因为穿过布隆过滤器的误判 key 数量已经很少即使 TTL 过期后再次穿透量级也是可控的。如果你连布隆过滤器都不想维护那就必须接受更高的数据库压力只能靠短 TTL 空值缓存降低重复请求的频率。这在低量级业务里可行但你要明确这是有代价的别等项目大了再回头补。3.4 第四层限流、降级与监控保证数据库不被打挂即便前面三层都到位也不能保证万无一失。数据库本身要有最后一道防线对数据库单库或单表设置 QPS 上限超过阈值后直接拒绝非核心查询。在服务层做熔断降级数据库响应变慢时优先保证写路径和核心读路径。监控指标要覆盖缓存命中率、空值缓存写入量、数据库查询耗时、SQL 返回空结果的比例。这里要强调一个观点防穿透的目标不是“让数据库永远不收到空查询”而是“让数据库收到的无效查询量级可控”。完全消灭不可能也不需要。只要量级控制住了数据库就能稳定服务正常请求。4. 互斥锁和请求合并要不要用什么时候用有些资料会把互斥锁也放进防穿透方案里这其实混淆了两个问题。互斥锁主要解决的是缓存击穿也就是热点 key 过期后避免大量线程同时去查数据库。它和穿透有关系但关系不是很多人想象的那样。4.1 互斥锁解决的是击穿不是穿透当某个 key 缓存 miss 后用分布式锁保证只有一个线程去数据库查询并回填缓存其他线程等待缓存回填完成后再读缓存。这个机制的前提是“数据库里真的有数据”否则回填的还是一个空结果等待的线程照样拿不到数据。所以如果查询的 key 本身不存在互斥锁并不能防止穿透。它只是把并行的数据库压力变成串行压力避免瞬间击穿数据库但每轮攻击照样会打到数据库一次。你只是让数据库从“被一万个线程同时打”变成了“被一个线程打然后其余线程等着”数据库压力并没有真正消失。4.2 实现方式和最常见问题以 Redis 分布式锁为例思路是value redis.get(key) if value null: lock redis.setnx(lock: key, 1, ttl 3s) if lock: row db.query(key) redis.set(key, serialize(row)) redis.del(lock: key) return row else: # 没拿到锁短暂 sleep 后重新读缓存 sleep(50ms) return redis.get(key)这里最常出问题的点有两个锁的 TTL 太短数据库查询还没结束锁就过期了其他线程拿到锁后重复查询等于锁白加。锁的 TTL 太长数据库查询失败时锁一直不释放导致后续请求全部阻塞接口超时率飙升。实际使用时要给锁设置合理的过期时间并加上失败重试和超时释放的兜底逻辑。不要以为加了锁就一定安全锁本身也是需要监控的。4.3 什么时候可以不用锁如果数据库查询本身很快比如主键查询在 5 毫秒内完成热点 key 过期瞬间的并发量又不到数据库上限那么完全不需要互斥锁直接放行反而更简单。互斥锁的价值在于热点 key 回填成本高比如复杂聚合查询耗时 200 毫秒以上、并发量又特别大的场景。回填成本低时锁带来的复杂度和潜在的线程阻塞风险不划算。一句话总结互斥锁是用来保护“热点 key 回填”的不是用来防穿透的。别把它和布隆过滤器、缓存空值混在一起选型。5. 生产环境选型按场景判断而不是按“最新方案”判断技术选型最怕的是为了用方案而用方案。防穿透的方案不能只看“哪种最前沿”要看当前业务的 key 构成、请求量、数据库压力和数据一致性要求。5.1 先看方案对比表下面把常见方案做一个对比方便直接对照选型。方案解决的核心问题内存开销实现复杂度数据一致性风险适合场景参数校验拦截非法请求无低无所有系统都必须做缓存空值重复查询不存在 key高空 key 多时膨胀快低有TTL 内新数据不可见少量 key 高频查询布隆过滤器拦截绝大多数不存在 key低亿级 key 几百 MB中需要维护低误判不致命key 全集可枚举、相对稳定互斥锁避免热点 key 回填并发无中无热点 key 回填成本高限流降级保护数据库上限无中无所有生产系统建议配置5.2 判断前先回答三个问题判断时先回答三个问题不存在的 key 是少量固定还是大量变化固定用空值缓存就够了变化必须上布隆过滤器。数据全集是否可枚举、相对稳定订单 ID、用户 ID、商品 ID 适合布隆过滤器搜索词、日志 ID 这类高基数且快速变化的 key 就不太适合因为你没法把全集及时同步进过滤器。数据写入后用户能不能容忍短时间查不到不能容忍空值 TTL 就必须设置得很短或者只在布隆过滤器误判时兜底。这三个问题回答完方案基本就清楚了不需要在技术选型会上反复争论。5.3 小团队和低频系统的务实做法如果是小团队、日请求量在万级以下数据库压力本来就不大我不建议一上来就引入布隆过滤器。布隆过滤器需要构建、加载、同步维护成本并不低。低频系统直接用短 TTL 空值缓存加参数校验就够了大部分场景撑得住。这类系统的正确做法是先加参数校验和基础限流再把空值缓存 TTL 控制在 1 到 3 分钟内同时监控空值缓存的数量。如果空值 key 数量开始快速增长说明请求模式异常再去考虑布隆过滤器。注意低频系统可以不上布隆过滤器但必须接受更高的数据库压力并且要监控空值缓存数量。这个“接受”是明确写在技术方案里的而不是出了问题再补救。5.4 大流量系统的检查清单如果是大流量系统特别是订单、支付、库存这类核心接口建议按这个清单逐项确认接口层是否对关键参数做了严格校验。布隆过滤器的数据源是哪个表构建脚本是否可重复执行。布隆过滤器误判率配置是多少有没有预留位数组空间。空值缓存 TTL 多长是否和业务一致性能接受的周期匹配。数据库连接池和 QPS 上限是否配置了保护值。空值缓存数量、数据库空结果比例、缓存命中率有没有监控告警。上线后是否存在数据删除后 key 仍然通过布隆过滤器进入查询的情况。这个清单每一项都可以在上线前检查完不需要等出了问题再补。6. 上线后被穿透了怎么排查和复盘防穿透方案上线后仍然可能出现数据库压力异常。这时候最重要的不是急着加缓存而是先确认现象到底是什么类型的问题。方向错了后面全白做。6.1 先确认现象是不是穿透三个指标可以帮你判断缓存命中率是否明显下降。穿透会导致命中率持续走低。数据库查询返回空结果的比例是否异常升高。如果空结果比例从 5% 涨到 60%大概率是穿透。请求的 key 是否有规律。比如连续递增、固定前缀、明显超出正常范围通常是脚本请求。如果只是数据库慢但空结果比例不高可能是慢查询、锁等待、连接池不足不要误判成穿透。这几类问题的处理方式完全不同。6.2 沿着调用链逐层看确认是穿透后按顺序排查先看接口层有没有把非法参数拦下来。没拦先补校验。再看布隆过滤器有没有生效。常见问题是过滤器没加载数据、加载的是旧数据、或者 key 序列化方式不一致导致查不到。再看空值缓存有没有写入成功。常见问题是 TTL 设置太短、Redis 内存达到上限后空值被淘汰、或者空值写入逻辑被异常分支跳过。最后看限流和降级有没有触发。如果这些都没配置数据库异常是必然的。一个容易被忽略的细节是 key 的序列化方式。布隆过滤器里存的是字符串业务代码里传入的是 Long 类型 ID如果转换时带上了类型信息两边就永远匹配不上。这类问题看起来像过滤器失效实际是代码细节没对齐。6.3 复盘时最该留意的几个数据复盘时不要只看“数据库有没有挂”要看几个能说明问题的数据穿透请求的 key 分布是集中在一小批还是分散在大量不同 key。集中说明可以被空值缓存解决分散必须靠布隆过滤器。空值缓存 key 数量变化曲线有没有在短时间内从几百涨到几十万。涨得快说明过滤器没生效或误判率偏高。布隆过滤器误判率实测值理论值不一定准确要结合线上数据评估。数据库空结果查询的 QPS 峰值用来判断是否需要继续加防线。这些数据能帮你判断当前的防线配置是过强还是不足。比如空值 key 数量涨得很快说明布隆过滤器误判率偏高或者没有真正生效需要重新构建过滤器。最后再强调一遍我个人的习惯先把参数校验做扎实再部署布隆过滤器空值缓存放在第三层兜底限流降级作为最后防线。这个顺序不是我拍脑袋定的而是每一层都解决前面一层漏过去的问题。缓存空值不是不能用但它应该是组合方案的一部分而不是你对缓存穿透的全部理解。
延伸阅读

更多相关文章

2026/9/9 16:49:55

线性系统大作业实战:状态空间建模与控制器设计全攻略

简介:上海交大仪器系研究生课程“线性系统分析与设计”的大作业资源包,面向需要完成同类课程任务或想系统掌握线性系统MATLAB实现的学生。压缩包共3个文件,包括2个m脚本和1个docx报告文档,整体约193KB,虽小但覆盖系统建…

2026/9/9 16:49:55

猫抓浏览器扩展完整指南:网页视频嗅探与下载入门

猫抓浏览器扩展完整指南:网页视频嗅探与下载入门 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 打开一个正在播放视频的网页&#xff…

2026/9/9 16:49:55

Solidity数据位置深度解析:Storage、Memory与Stack实战指南

写Solidity合约和写普通后端程序最大的不同,就是你对数据放哪儿这件事必须非常敏感。Storage、Memory、Stack这三个词,几乎每个Solidity文档都在讲,但真正用起来,大多数人还是会栽跟头。我见过不少项目,功能跑通了&…

2026/9/9 17:50:03

Sunshine:家庭游戏串流服务器完全实操手册

Sunshine:家庭游戏串流服务器完全实操手册 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 在客厅电视上玩 PC 游戏,到卧室平板上无缝接着打,是很…

2026/9/9 17:50:02

无线话筒综合文档解析:核心参数与现场应用指南

简介:无线话筒.rar是一份以无线话筒技术为核心的综合文档,面向音频工程师、电子爱好者,以及会议、演出、教学等场景的技术保障人员。压缩包内共12个文件,既有电路原理图与PCB设计文件,也有工程结构文件、编译报告、日志…

2026/9/9 17:50:02

便携式溶解氧测定仪:防水防尘抗跌落,恶劣环境照常精准

便携式溶解氧测定仪这几年在野外水质调查、水产养殖、污水处理和环保监察场景里越来越常见。但很多人选表的时候只看精度和量程,拿到手才发现,真正决定一台表能不能陪你长期跑现场的,往往是那些容易被忽略的细节:防不防水、耐不耐…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/9 0:00:48

MHS模型硬件标准:让大模型像调用软件一样控制物理设备

让Claude真正看着显微镜说“这个细胞形态不太对”,或者让大模型自己调一版机械臂的运动轨迹,这事儿听上去已经很接近科幻片了。但你真上手试一次就会发现,模型不缺智商,缺的是一个能插进显微镜、机械臂、激光控制器里的“通用插座…

2026/9/9 0:00:48

AI五大核心方向详解:从机器学习到大模型,零基础转行选哪条?

会有人告诉我,他想转行学AI,但打开招聘网站一看直接傻眼:机器学习、深度学习、自然语言处理、计算机视觉、大模型应用……满屏都是这些词,好像每个都会一点,又好像每个都离自己很远。还有人上来就问“学Python还是学Ja…

2026/9/9 0:00:49

从50行最小循环到生产级AI引擎:工程化改造全解析

直接说干货。这一章我写的不是那种"hello world跑通某个模型"的教程,而是把AI引擎当做一个真正要上线、要被人调用、要扛流量的系统来聊。从最初只有50行的最小循环,到能够承载生产流量的AI引擎,中间差的不是代码量,而是…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

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

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

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