资讯动态

缓存穿透、击穿、雪崩:从成因到解决方案全解析

发布时间:2026/9/16 2:05:21 来源:尧图企业网站定制
先聊个真实场景。某个周末下午运营在群里喊“首页活动页打不开了”我登录监控面板一看Redis 的keyspace_hits和keyspace_misses两条曲线交叉成了一把剪刀数据库连接数直接拉满慢查询日志里全是同一个 SQL。重启 Redis 之后数据恢复了但过几分钟又被打回原形。后来排查下来问题出在活动页关联的商品 ID 不存在请求一路穿透到 MySQL把库打爆了。这就是经典的缓存穿透。缓存问题里穿透、击穿、雪崩是面试和实战中出现频率最高的三个词。很多人能背出概念和解决方案但真在系统里遇到时容易分不清当前到底是哪一种更别提怎么一步步定位和恢复。这篇就把这三个问题掰开揉碎从成因、危害到完整的落地方案再到实际排查链路一次讲透。1. 三兄弟的区别穿透、击穿、雪崩到底分别指什么先别急着上代码。如果连问题都分不清方案再全也是乱打。穿透、击穿、雪崩虽然都是“缓存没拦住请求压力落到数据库”但触发场景完全不同。1.1 一张表看懂三者的本质区别对比维度缓存穿透缓存击穿缓存雪崩查询的数据数据本身不存在热点 key 存在但刚好过期大量 key 同时过期并发特征大量不同 key 的无效请求并发集中在同一个 key大量 key 并发被请求导致原因恶意攻击、错误参数、数据未初始化热点 key 过期瞬间批量 key 设置了相同过期时间、Redis 宕机数据库压力持续打到不存在的数据瞬间打到单个热点数据瞬间打到一大批数据典型场景刷接口、非法 ID、异常参数微博热搜、爆款商品详情缓存集中失效、灾备切换1.2 穿透查了根本不存在的数据缓存穿透核心有两个字不存在。一个请求进来先去 Redis 查没查到再去数据库查数据库里也没有那这个空结果没有任何缓存价值。如果这类请求量一旦大起来Redis 就像个摆设每一次请求都直扑数据库。最典型的是数字 ID 自增的接口。比如商品 ID 是 1 到 100000攻击者不断请求GET /product/detail/100001、100002、-1这种库里根本不存在的 IDRedis 缓存永远查不到数据库在没有任何保护的情况下被反复查询。我见过一个系统线上 Redis 命中率白天一直稳定在 98% 以上某天突然跌到 60%数据库 CPU 升高到 90%排查后发现就是有人写了个脚本循环遍历不存在的用户 ID 调接口。这不是偶发问题而是典型的低门槛攻击手段。1.3 击穿唯一热点 key 在瞬间过期缓存击穿的关键词是“热点 key 过期”。某个 key 平时有大量的并发请求比如秒杀商品、热门话题下的 feed 流。这个 key 在 Redis 里一直是热的突然在某个时刻过期了。过期之后第一个请求发现没有缓存去数据库查紧接着后面成千上万个请求也发现没有缓存全部一拥而上数据库瞬间被打垮。这里和穿透最大的区别是击穿的数据在数据库里是存在的只是缓存过期了穿透的数据数据库里压根没有。击穿是“一个 key 扛下了所有并发却在最不该倒下的时刻倒了”。1.4 雪崩大量 key 在同一时间失效雪崩看名字就知道是连锁反应。大量 key 在同一个时间点集中过期或者 Redis 实例直接宕机导致所有缓存失效。此时大量请求同时落到数据库数据库连接被耗尽端口拒绝新请求整个服务可能进入雪崩式的连环故障。雪崩最容易出现在“批量初始化缓存”的场景里。运营在后台一次性上架了一批商品程序给每个商品都设置了相同的缓存过期时间比如统一 30 分钟后过期。那这 30 分钟一到这批商品的缓存同时失效如果这批商品恰好是首页主推款流量高峰一来数据库就会同时收到大量查询。除了过期时间集中的情况Redis 主从切换、集群扩容时也容易出现短暂不可用效果等同于雪崩。2. 缓存穿透黑客最爱的“空查询攻击”与三种对抗手段缓存穿透是三个问题里最容易被攻击利用的因为没有正常的业务逻辑会把大量“查不到的数据”循环请求出来。一旦你的接口暴露在公网环境穿透攻击几乎防不胜防。2.1 穿透的完整链路分析一个常规读接口的链路是请求进来 - 查 Redis - 命中直接返回 - 未命中查 MySQL - 结果回写 Redis。穿透场景下查 Redis 未命中查 MySQL 也没数据那 Redis 里永远不会有缓存下一次同样的请求又来一遍。等于缓存层对这类请求完全没有加速和过滤作用。细想一下穿透的破坏力取决于请求量和“不存在请求”的比例。正常用户偶尔误操作产生少量无效请求数据库扛得住问题不明显。但攻击者可以把无效请求的比例刷到 100%每个请求还故意用不同的参数比如手机号、订单号、身份证号这种高基数数据让后端无法通过简单的调用频率来限流。2.2 方案一缓存空值最基础也最有效既然数据库没有数据那我们就在缓存里存一个“空值”。核心逻辑是当数据库查询结果为 null 时在 Redis 里写入一个短暂的占位 key比如值设置为空字符串过期时间设置为 60 秒。后续同样参数的请求过来直接命中这个空值返回“数据不存在”不再打到数据库。public Product getProduct(Long productId) { String cacheKey product: productId; String cacheValue redisTemplate.opsForValue().get(cacheKey); if (StringUtils.isNotEmpty(cacheValue)) { // 缓存命中直接返回 return JSON.parseObject(cacheValue, Product.class); } // 缓存未命中查数据库 Product product productMapper.selectById(productId); if (product null) { // 数据库也没有缓存空值防止穿透 redisTemplate.opsForValue().set(cacheKey, , 60, TimeUnit.SECONDS); return null; } redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(product), 300, TimeUnit.SECONDS); return product; }这个方案有几个细节必须处理好。空值的过期时间不能太长60 秒内可能数据已经写入库里了如果空值缓存太久会导致数据一直查不出来。建议控制在 30 到 60 秒。还要注意空值数量膨胀的问题攻击者请求一万个不存在的 ID你就得存一万个空值 keyRedis 内存会被占用。因此可以加一个 Per-Key 的过期时间并且定期清理空值缓存。2.3 方案二布隆过滤器从源头拦截非法 key缓存空值是对“查询结果”做的事情布隆过滤器则是在“查询之前”就把不可能的 key 挡在门外。布隆过滤器是一种概率型的数据结构它可以告诉你“这个数据一定不在集合里”或者“这个数据可能在集合里”。实现思路是系统初始化时把所有合法的商品 ID、用户 ID 全量加载到布隆过滤器里。请求过来先判断 key 是否在过滤器里如果不在直接返回空完全不查 Redis 和数据库。如果在才走正常的缓存查询链路。Component public class BloomFilterHelper { private BloomFilterLong bloomFilter; PostConstruct public void init() { // 预估数据量 100 万误判率 0.01 bloomFilter BloomFilter.create(Funnels.longFunnel(), 1000000, 0.01); // 从数据库加载全量 ID ListLong ids productMapper.selectAllIds(); ids.forEach(bloomFilter::put); } public boolean mightContain(Long id) { return bloomFilter.mightContain(id); } }注意布隆过滤器有误判率。它会误判“不存在的数据为存在”但绝不会误判“存在的数据为不存在”。也就是说如果一个合法 ID 被误判为不存在那用户就看不到这个商品了。这是不能接受的。因此布隆过滤器一般用于“key 集合相对固定、误判可以兜底”的场景。比如说如果布隆过滤器放行了一个不存在的 ID后续还有缓存空值方案兜底那误判的影响就很小但如果合法 ID 被过滤掉就必须考虑数据更新时同步更新布隆过滤器。还有一种基于 Redis 自身的 bitmap 实现方案适合 ID 是数字且分布连续的场景。比如把某个 ID 是否存在于集合中映射到位图的第 N 位查询时判断对应位是否为 1。这个方案的缺点是只能应对“连续 ID 简单标记”的场景一旦 ID 分布不规则位图长度和内存消耗会很难看。2.4 方案三参数校验与限流必要的最后一道防线不要把所有希望都寄托在缓存和过滤上。对于明显的异常参数在接口入口就要拦截。比如 ID 必须为正整数、ID 不能超过某个最大值、ID 不能等于 0 或负数。这些校验逻辑成本极低但能把最低级的刷接口行为挡掉一大半。限流是更通用的手段。对于同一个 IP、同一个用户维度的请求频率做控制超过阈值直接拒绝。可以用 Redis 的INCR加过期时间实现一个简单的计数器限流也可以用 Sentinel 这类框架做参数限流。穿透攻击的请求特征很明显往往集中在某一批接口上按接口维度设置 QPS 上限能在缓存被击穿之前先拦掉一部分流量。2.5 布隆过滤器的工程落地细节这里分享几个布隆过滤器在真实项目里的落地细节网上教程很少说。第一全量加载的时机。如果是在PostConstruct里把全量 ID 加载到布隆过滤器那应用每次启动都会做一次全表查询。商品表十万条数据还好如果是千万级用户表这个操作会导致应用启动时间变长甚至启动时数据库就被拖慢。更稳妥的做法是启动时加载一份“小表”的关键 ID比如最近 30 天有转化的商品再通过异步任务在后台逐步把全量 ID 刷进去。第二布隆过滤器的重建策略。数据库新增了大量合法 ID 后布隆过滤器要能感知到。一种做法是每天凌晨重建一次过滤器另一种是提供触发刷新的接口供运营手动调用。第三监控误判率。布隆过滤器的误判率会随着数据量增加而上升如果发现空值缓存的数量异常增多有可能是误判率升高了需要重新评估初始容量。3. 缓存击穿热点 key 过期的那一秒后端都在裸奔缓存击穿是三个问题中“最让人猝不及防”的。它平时看起来没有任何异常秒杀开始前系统一切正常秒杀开始瞬间流量猛增偏偏这个热点 key 在关键时刻过期了整个系统火力全开去打数据库。3.1 单 key 失效为什么会拖垮数据库很多人对击穿的理解是“就一个 key 而已怎么会把数据库打垮”。问题的关键在于并发集中在同一个 key。假设某个商品详情页同时有 10 万个并发请求正常情况下 Redis 每秒能扛下这些请求数据库几乎无压力。一旦这个 key 过期10 万个请求同时发现缓存未命中同时向 MySQL 发起查询。MySQL 的并发能力一般在几千 QPS 量级10 万请求瞬间涌入连接池直接耗尽后面的请求全部排队超时进而引发服务雪崩。所以击穿是“单点并发”问题它的危害取决于这个 key 上的并发强度而不是 key 的数量。这也是为什么微博热搜、明星八卦、爆款商品这种场景最容易出问题。3.2 方案一互斥锁只让一个请求去查数据库互斥锁的思路是当某个 key 过期时不让所有请求都去查数据库而是让它们去抢一把锁只有抢到锁的请求才能查数据库其他请求要么等待要么直接返回旧值。由于数据库查询被串行化数据库压力就从“10 万并发”变成了“1 个查询”。public Product getProductWithLock(Long productId) { String cacheKey product: productId; String cacheValue redisTemplate.opsForValue().get(cacheKey); if (StringUtils.isNotEmpty(cacheValue)) { return JSON.parseObject(cacheValue, Product.class); } // 缓存未命中尝试获取分布式锁 String lockKey lock:product: productId; String requestId UUID.randomUUID().toString(); boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, requestId, 3, TimeUnit.SECONDS); if (locked) { try { // 双重检查防止拿到锁后缓存已经被其他线程重建 cacheValue redisTemplate.opsForValue().get(cacheKey); if (StringUtils.isNotEmpty(cacheValue)) { return JSON.parseObject(cacheValue, Product.class); } Product product productMapper.selectById(productId); redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(product), 300, TimeUnit.SECONDS); return product; } finally { // 只释放自己持有的锁 if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) { redisTemplate.delete(lockKey); } } } // 没拿到锁先等待一段时间再查缓存 try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return getProductWithLock(productId); }互斥锁方案有几个容易踩的坑。锁必须设置过期时间否则持锁线程挂了锁永远不会释放所有请求阻塞。释放锁时要先判断是不是自己持有的锁防止因为锁过期后又被其他线程设置导致误删。这里用 requestId 作为锁的值来避免这个问题。等待时间的策略也很重要Thread.sleep(50)是简单粗暴的做法更好的做法是利用 Redis 的订阅机制在锁释放时通知等待线程立刻重试减少空转等待。3.3 方案二逻辑过期缓存永远不失效的“假过期”逻辑过期本质上不是让 key 过期而是在 value 里额外存一个过期时间字段。查询时即使逻辑上已经过期了也直接返回旧值同时异步地去更新缓存。这种方案的好处是查询请求永远不会直接打到数据库性能最好。public Product getProductWithLogicalExpire(Long productId) { String cacheKey product: productId; String cacheValue redisTemplate.opsForValue().get(cacheKey); if (StringUtils.isEmpty(cacheValue)) { // 缓存中完全没有数据说明缓存尚未构建走互斥锁走查库 return loadFromDb(productId); } // 解析缓存中的逻辑过期时间 CacheProduct cacheProduct JSON.parseObject(cacheValue, CacheProduct.class); if (cacheProduct.getExpireTime() System.currentTimeMillis()) { // 逻辑上未过期直接返回 return cacheProduct.getProduct(); } // 逻辑上已过期尝试获取锁获取成功后异步刷新缓存 String lockKey lock:product: productId; boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 3, TimeUnit.SECONDS); if (locked) { // 另起线程更新缓存当前线程直接返回旧值 CompletableFuture.runAsync(() - { Product product productMapper.selectById(productId); CacheProduct cacheData new CacheProduct(product, System.currentTimeMillis() 300000); redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(cacheData)); redisTemplate.delete(lockKey); }); } return cacheProduct.getProduct(); }逻辑过期的核心设计是“永远不主动删除缓存”。业务上把 key 的 Redis TTL 设置成很大的值比如 30 天然后在 value 里自己管理过期时间。每次查询判断 value 里的 expireTime 是否大于当前时间如果大于直接返回如果小于说明缓存里的数据该更新了但先不阻塞请求异步去更新缓存。这个方案的缺点是数据可能短暂不一致。逻辑过期时间到了之后请求会先拿到旧数据等后台线程更新完成后才变成新数据。对一致性要求高的场景不推荐但对商品详情页、首页推荐流这类读多写少、一致性要求没那么极端的场景来说效果非常好。3.4 热点 key 的识别与预热互斥锁和逻辑过期解决的是“已经发生”的击穿但真正有经验的人会做热点 key 的提前识别。在流量高峰到来之前主动把热点数据加载到 Redis 里并设置合适的过期时间让它在高峰期间不过期。热点 key 的识别有两类来源。一类是业务侧的明确规划比如电商平台知道 6 月 18 日零点有秒杀活动提前一天把秒杀商品信息预热到缓存。另一类是运行时动态识别通过监控 Redis 的访问频次发现某个 key 的 QPS 突然上升自动把它标记为热点 key 并延长过期时间或者直接不让它过期。开源方案有京东的 hotkey 框架原理是客户端上报 key 访问次数到 worker 节点worker 汇总热点数据下发到各服务端让热点 key 常驻内存。这类框架比较适合大型系统中小项目用 Redis 的INFO命令配合日志分析也能做到基础的识别。4. 缓存雪崩批量 key 同时失效的连锁反应缓存雪崩的破坏力是三个问题里最大的。穿透和击穿影响的可能是单个 key 或者某类非法请求雪崩影响的是一大片业务。很多系统在高峰期突然崩溃事后一查Redis 里大量 key 同时失效。4.1 雪崩的两种触发路径雪崩有两条完全不同的触发路径处理方式也不同。第一条路径大量 key 的过期时间相同。这通常是代码设计问题。比如批处理任务往 Redis 里写 10 万个商品缓存统一设置了 1 小时的过期时间。任务每天 0 点执行那每天 1 点整10 万个 key 同时过期如果此时有大量用户访问这些商品数据库瞬间收到 10 万个不同商品的查询请求。注意这和击穿不一样击穿是 10 万个请求打同一个 key雪崩是 10 万个请求打 10 万个不同的 key但结果都是数据库被打爆。第二条路径Redis 实例宕机。所有缓存同时不可用所有请求全部进入数据库。处理方式也完全不同前者靠“错峰过期”解决后者靠高可用架构和服务降级解决。4.2 过期时间加随机值成本最低的防御针对第一条路径最简单的方案是给过期时间增加一个随机偏移量。原本是“固定 300 秒过期”改成“300 到 420 秒之间随机过期”。这样原本同一时间过期的 key就会在 300 到 420 秒这个区间内分散过期避免集中失效。int baseExpireTime 300; int randomExpireTime baseExpireTime new Random().nextInt(120); redisTemplate.opsForValue().set(cacheKey, value, randomExpireTime, TimeUnit.SECONDS);这个方案成本低、效果直接我建议所有“批量为多个 key 设置缓存”的代码都加上随机过期时间。不仅是线上业务代码包括定时任务批量预热的逻辑也要加。有时候你单独看每个 key 的过期时间只有几分钟的差异但几百上千个 key 加在一起数据库压力就能明显被削平。4.3 Redis 高可用与多级缓存兜底针对 Redis 宕机导致的雪崩核心思路是“别让 Redis 成为单点”。部署主从加哨兵模式或者直接上 Redis Cluster 集群会让单个 Redis 实例宕机时自动切换到从节点业务无感知。但即便是集群也有全量宕机的可能所以还需要多级缓存兜底。在 Redis 前面加一层本地缓存比如 Caffeine 或 Guava Cache。查询链路变成本地缓存 - Redis - 数据库。Redis 宕机时本地缓存依然能扛住一部分请求给数据库和恢复争取时间。本地缓存需要注意一致性问题因为多台机器上的本地缓存各自独立更新一刀切成本高一般只用于访问量特别大的热点数据。多级缓存还有一个隐藏的好处当 Redis 不可用时本地缓存的命中率能挡住一部分流量避免数据库被瞬间打崩。我在实际项目中就见过Redis 主节点故障切换期间正是靠本地缓存撑住了高峰期的流量让团队有充足的时间处理故障。4.4 服务降级和限流熔断最后一层保命符不管前面做了多少措施总会有意外。雪崩发生时如果数据库已经濒临崩溃必须牺牲一部分非核心功能保住核心链路。这就是服务降级。降级策略一般这么设计当缓存和数据库都无法正常响应时不直接抛出错误而是返回一个默认值或者旧版本的数据。比如商品详情页返回“商品已下架”的兜底文案而不是让用户看到 502 页面。更极端的做法是对非核心接口直接返回“系统繁忙”把有限的资源留给核心交易链路。限流熔断的技术选型我推荐 Sentinel 或 Hystrix。以 Sentinel 为例可以给依赖数据库的查询接口设置一个 QPS 阈值超过阈值的请求直接走 fallback 逻辑。这样数据库永远不会被打到极限系统虽然损失了一部分流量但不至于全盘崩溃。这一点在雪崩场景里是最后的保命符。5. 实战中的排查链路与监控指标很多文章讲完了方案就结束了但实际运维中更重要的是线上真的出问题了你怎么判断是穿透、击穿还是雪崩这里分享一次完整的排查链路。5.1 一次缓存命中率暴跌的排查过程有次系统报警Redis 命中率从 97% 跌到 70%数据库 CPU 飙升到 80%。我当时按这个顺序排查第一步先看 Redis 监控指标。注意到keyspace_misses每秒突然涨到几万命中率下降趋势和 misses 上升趋势完全吻合。同时connected_clients没有明显变化这说明不是 Redis 本身不可用。第二步看数据库慢查询。慢查询日志里出现了大量同一条 SQL都是查商品数据的但 where 条件里的 ID 各不相同。这基本排除了击穿的可能因为击穿是同一个 ID 打爆数据库。第三步抽样看请求参数。从网关日志里随机取了 100 条请求发现有一半以上的请求 ID 都不是正常商品 ID。有的 ID 是负数有的是超出合理范围的大数。到这里基本确认是缓存穿透。第四步写脚本查这些非法 ID 的访问来源。发现来自同一个 IP 段且 User-Agent 和正常客户端完全不一致。确定是攻击行为。处理方式先封禁来源 IP然后在商品查询接口入口加 ID 校验把负数、超大数直接拦截再启动空值缓存策略。整套流程从发现到最后解决大约用了 40 分钟。事后我反思了一下如果一开始就布隆过滤器攻击者根本连 Redis 和数据库的毛都摸不到。5.2 需要盯住的几个 Redis 监控指标keyspace_hits/keyspace_misses缓存命中情况。misses 突然升高第一反应是穿透或者批量 key 过期。expired_keys每秒过期的 key 数量。如果这个指标有规律的尖峰说明有批量 key 集中过期的风险。connected_clients连接数。连接数突然暴涨往往是大量请求没走缓存正在等待数据库响应。instantaneous_ops_per_secRedis 每秒操作数。正常波动不大突然上涨可能意味着缓存写入异常比如空值缓存过多。evicted_keys被淘汰的 key 数量。如果内存不够导致 key 被主动淘汰效果等同于缓存雪崩。这些指标可以在 Redis 的INFO stats命令里直接看到生产环境建议接入 Prometheus Grafana 一类的监控系统周期采集并告警。5.3 三大问题同时出现的复盘模板线上故障往往不是单一问题。比如穿透攻击进来时大量无效 key 查询占了 Redis 的处理能力热点 key 的缓存更新变慢一旦热点 key 过期击穿也跟着出现热点 key 被打穿后数据库连接耗尽其他缓存查询超时最终演变成全链路雪崩。所以复盘时不要只看表象。我一般用下面的模板来梳理触发点是什么是异常请求、热点过期还是 Redis 故障哪些 key 或接口受影响把 key 的维度列出来。Redis 在故障期间的表现如何命中率、过期 key 数、OPS、内存变化。数据库的哪些指标最先恶化慢查询、连接数、CPU、磁盘 IO。现有的防御手段为什么没生效是方案没做还是做了但参数设置不合理恢复后如何验证除了缓存预热还要确认攻击源是否已拦截监控告警是否正常。每次按这个模板复盘你会发现自己对缓存的理解在变深很多问题其实是系统性漏洞不是单个方案能解决的。6. 方案选型误区与进阶延伸最后一个部分聊一些经常容易搞错的地方以及三大问题和缓存领域其他常见话题的关联。6.1 别把缓存穿透当击穿处理我见过不少初级开发者一看到缓存没有命中就加互斥锁结果穿透攻击打过来每个 key 都触发一次锁竞争锁本身成了新的瓶颈。记住互斥锁和逻辑过期是击穿的方案解决的是“热点 key 存在但缓存临时失效”的问题穿透必须先判断数据是否存在空值缓存和布隆过滤器才是对症的药。有一种简单有效的判断方法看数据库里有没有这条数据。如果数据库里也没有那肯定不是击穿。穿透和击穿的本质区别不在流量和并发而在“数据是否存在”。这一点应该在方案设计初期就想清楚。6.2 与缓存一致性、分布式锁、序列化的关联很多人在学 Redis 时会发现穿透、击穿、雪崩不是孤立的知识点。它们和搜索热搜里的缓存一致性、分布式锁、序列化都有密切关系。先说缓存一致性。空值缓存和逻辑过期都会引入短暂的数据不一致。空值缓存可能导致数据库新写入的数据在空值过期前查不到逻辑过期则一定会让用户看到旧值。所以缓存一致性要求高的系统比如库存、余额不能随便用逻辑过期方案。分布式锁是击穿方案中互斥锁的基础。上文代码里使用的是SETNX加过期时间的方式这是最简单可靠的分布式锁实现。生产环境里有人用SETNX之后忘记设过期时间锁就变成了一把死锁这是一个非常经典的坑。序列化和缓存击穿也有关系。如果序列化框架效率低下缓存数据占用的内存更大Redis 内存满后会触发淘汰策略被淘汰的 key 在下一个请求中必然走数据库相当于主动制造了一波微型击穿。所以选择像 Fastjson、Jackson、Protobuf 这类高效的序列化方式不仅是性能问题也是间接影响缓存稳定性的因素。6.3 根据业务体量选择方案方案没有绝对的好坏只有是否匹配当前业务体量。小型项目、接口 QPS 在几百级别做好参数校验 缓存空值基本够用不需要上布隆过滤器。中型项目、接口 QPS 在几千到几万建议完整实现参数校验 空值缓存 互斥锁/逻辑过期 过期时间随机化。大型项目、接口 QPS 在十万级以上在上一档基础上补充布隆过滤器、热点 key 识别、多级缓存、Sentinel 限流降级。方案上线后要做验证。最简单的方法是模拟异常场景手动把一个热点 key 删掉观察数据库 QPS 是否突然飙升用脚本连续请求不存在的 ID观察空值缓存是否生效。这种演练比看文档有用得多。我个人在实际操作中的体会是三大缓存问题方案都不难难的是在系统设计初期就把它们都纳入考虑。很多时候不是团队不会解决而是问题已经造成损失了才回头补方案。如果你现在正在做一个新的缓存模块花半天时间把这三种场景的防御代码写好绝对比以后花两天时间去排查线上故障划算得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价