▌ 技术引导
缓存穿透、击穿、雪崩是分布式系统中极具破坏力的三种缓存问题。穿透是因为查询一个不存在的Key导致大量请求穿透到数据库,压垮后端;击穿是热点Key过期后瞬间大量并发请求涌入数据库;雪崩则是大量缓存同时失效,导致系统整体负载激增。我亲测在高并发场景下,使用Redis+Lua脚本+布隆过滤器+热点Key永不过期+本地缓存+异步刷新的组合,能有效降低这三类问题的冲击。实际部署时,布隆过滤器的误判率要控制在0.1%以内,热点Key的过期时间要设置为十倍于正常请求频率,本地缓存用Caffeine,异步刷新用RabbitMQ,监控用Prometheus+Grafana。这组合成本可控,但配置细节容易出错,比如误判率过高会触发大量无效查询,配置不当又容易导致缓存失效后数据库压力过大。真实生产中,我见过有人把布隆过滤器误判率调到1%,结果害得数据库直接挂掉,后来紧急回滚才恢复。
在实际操作中,热点Key的预热策略很关键。我见过在双十一大促期间,将商品详情页的Key预热到本地缓存,再通过异步任务去更新Redis,避免了击穿。布隆过滤器的部署要谨慎,必须与Redis主从架构配合,防止过滤器的误判导致后端压力上升。本地缓存的并发策略也要考虑,比如Caffeine的并发度和初始容量配置不准确,容易导致缓存命中率下降,反而增加穿透概率。异步刷新的队列需要设置合理的延迟与重试策略,避免消息堆积或刷缓存失败。
整个方案的落地需要结合业务特征,比如支付类业务不能容忍缓存延迟,所以异步刷新必须有兜底机制。我在某支付平台实战中,用Redis+Lua实现查询校验,用本地缓存+异步刷新实现热点Key保护,布隆过滤器仅用于过滤非敏感数据,避免误判。配置时,布隆过滤器的bitSize根据数据量估算,比如100万条Key,设置bitSize为10000000,误判率控制在0.1%。热点Key的过期时间根据业务访问频率动态调整,比如查询频次高的Key设置为30天,查询频次低的Key设置为10秒。
同时,我亲测在使用本地缓存时,如果没设置合适的淘汰策略,会占用太多内存,导致JVM OOM。Caffeine的回收策略选择基于大小还是基于时间,要根据业务场景决定。如果业务是实时性强的,那么基于时间的回收更合适;如果是数据量大但更新频率低,基于大小的回收能更有效控制内存。另外,监控指标需要覆盖缓存命中率、穿透率、雪崩次数、击穿峰值等,这些都是系统稳定性的重要指标。
在实践中,我见过有人直接把Redis的过期时间设为0,试图解决击穿问题,结果反而导致缓存成为硬性依赖。正确的做法是让热点Key永不过期,或者通过定时任务定期刷新。需要特别注意的是,不能把所有Key都做热点处理,否则会浪费内存与CPU资源。另外,缓存穿透和雪崩虽然本质不同,但都可以通过预热、过滤、监控来缓解。最终解决方案必须结合业务特征,不能一刀切。
▌ 技术参考
一
缓存穿透是指查询一个不存在的Key,导致大量无效请求直达数据库。在Java生态中,最常见的是用Redis+布隆过滤器的组合。布隆过滤器的bitSize计算公式是bitSize = (n ln(0.5)) / ln(1 - (1/m)^k),其中n是预期插入的元素数量,m是bit数组大小,k是哈希函数数量。我亲测bitSize如果设置过小,误判率会飙升,比如用10000000作为bitSize,当数据量超过200万时,误判率会超过0.5%。这时候需要增加bitSize,或调整k值,比如用3个哈希函数。在Spring Boot中,可以用Guava的BloomFilter配合Redis做穿透拦截。
二
缓存击穿通常出现在热点Key过期后,短时间内大量请求涌入数据库。解决方法有两种:永不过期+本地缓存预热,或使用互斥锁+异步刷新。我见过有人用Redis的SETNX命令加锁,但容易导致死锁,尤其是在多节点部署时。后来改用Redisson的RedLock机制,设置锁的过期时间为Key过期时间+30秒,避免锁超时后无法释放。同时,热点Key的过期时间要根据业务特征设定,比如商品详情页设置为30天,而用户登录状态可能设置为5分钟。在配置中,要确保本地缓存的并发级别足够高,比如用Caffeine的concurrencyLevel=16,防止缓存命中时冲突。
三
缓存雪崩是大量缓存同时失效,导致数据库负载激增。主要原因是缓存配置了相同的过期时间,且未做预热。我亲测在电商平台中,某次促销活动导致大量商品Key同时过期,数据库瞬间响应延迟达到200ms。这时候必须把缓存过期时间分散,比如在正常过期时间基础上随机加减30秒。具体操作可以用Redis的EXPIRE命令配合随机数生成,例如:EXPIRE key $[RANDOM]。同时,可以利用本地缓存做兜底,比如Caffeine的maximumSize=10000,这样即使Redis全部失效,本地缓存还能支撑一部分流量。
四
布隆过滤器的使用场景要精准。我见过有项目把所有Key都加到布隆过滤器中,结果导致内存占用过高,CPU利用率飙升到80%以上。所以,布隆过滤器应该只过滤非敏感数据,如用户查询的不存在ID,而不能用于关键数据如支付记录或订单信息。在实际部署中,还要考虑布隆过滤器的更新策略,比如每次写入新Key时都要同步更新过滤器,否则会漏掉新数据。另外,布隆过滤器的误判率要动态监控,比如通过Redis的INFO命令查看命中率,当误判率超过0.1%时,必须调整bitSize或k值。
五
热点Key的保护策略需要结合本地缓存与异步刷新。我亲测在双十一大促期间,将商品Key的过期时间设为30天,同时用本地缓存做预热,这样即使Redis挂了,本地缓存还能维持一部分服务。在本地缓存的配置中,要设置合适的maximumSize和expireAfterAccess,比如maximumSize=50000,expireAfterAccess=10m,这样能有效控制内存占用。异步刷新可以用RabbitMQ做消息队列,用一个独立的消费者线程周期性地刷新Redis缓存。在配置消息队列时,要避免堆积,比如设置合理的QoS策略和ACK机制。
六
在处理雪崩问题时,可以使用缓存冷启动机制。我见过有些服务会在系统启动时,通过定时任务批量加载缓存到Redis。例如,在Spring Boot中配置一个StartupCacheTask,使用@Scheduled注解定时执行,加载热门数据。这种方法能避免系统刚启动时缓存为空,导致数据库压力过大。同时,要确保冷启动任务线程池足够大,比如使用ThreadPoolExecutor设置corePoolSize=5,maximumPoolSize=10,避免任务堆积。
七
互斥锁的使用要谨慎。在Redisson中,可以通过RLock接口实现分布式锁,但必须设置合理的锁超时时间,比如在Key过期时间基础上再加30秒。我亲测如果没设置超时,会因为锁未释放导致其他节点无法获取锁,进而引发竞争。另外,锁的粒度要细,比如对单个Key加锁,而不是对整个查询接口加锁。这样可以避免多个Key同时失效时,锁机制失效导致数据库过载。
八
在配置Redis的过期策略时,要避免全局统一设置。我见过有人把所有Key的过期时间设为5分钟,结果导致冷门数据频繁失效,反而增加数据库压力。正确的做法是按Key类型设置不同的过期时间,比如订单信息过期时间设为1小时,商品信息设为7天。在Redis的配置文件中,可以通过TTL命令手动设置,或者用Lua脚本动态调整。实际监控中,发现某个Key的过期时间设置过短,可以通过KEYS命令查找,并修改TTL。
九
本地缓存的淘汰策略要合理。Caffeine默认使用基于大小的淘汰策略,但有些业务可能需要基于时间的淘汰。比如在支付系统中,用户会话信息可以设置为过期后自动删除,防止缓存堆积。在配置文件中,可以通过maximumWeight和weigher参数控制淘汰策略。如果使用基于时间的策略,设置expireAfterAccess=5m,这样用户访问后5分钟内缓存有效。同时,要确保本地缓存的写入策略与Redis保持同步,避免数据不一致。
十
异步刷新的实现需要结合消息队列和缓存更新服务。我见过在实际部署中,使用RabbitMQ做消息队列,用一个独立的Spring Boot应用消费消息,执行缓存更新。在消息队列的配置中,要设置好持久化与确认机制,比如basicQos=1,ackMode=MANUAL。另外,更新缓存的线程池要独立于主业务线程池,防止主业务阻塞缓存更新。在Java代码中,可以通过@RabbitListener注解监听队列,并使用RedisTemplate执行SET命令更新缓存。
十一
监控是缓存问题治理的核心。我亲测在Prometheus监控中,需要配置缓存命中率、穿透率、雪崩次数、响应延迟等指标。例如,缓存命中率可以用redis_cache_hit_rate{job="redis"},穿透率可以用redis_cache_punch_rate{job="redis"}。在Grafana中,可以创建仪表盘展示这些指标。另外,要设置告警规则,比如当缓存穿透率超过0.5%时触发告警,帮助及时发现和处理问题。
十二
在Redis中使用Lua脚本可以有效避免穿透和击穿。我亲测在用户查询接口中,用Lua脚本校验Key是否存在,先通过布隆过滤器判断,再用Lua脚本执行查询。例如,Lua脚本可以这样写:
local key = KEYS[1]
local exists = redis.call('EXISTS', key)
if exists == 1 then
return redis.call('GET', key)
else
return false
end
这种方案能减少不必要的数据库查询,提高系统吞吐量。同时,要注意Lua脚本的执行时间,如果脚本太长,会导致Redis阻塞,影响整体性能。
十三
缓存预热需要结合业务数据特征。我见过在电商平台中,提前加载商品库存、促销信息到Redis,防止大促期间雪崩。预热策略可以用定时任务或消息队列触发。例如,在Spring Boot中配置一个ScheduledTask,使用RedisTemplate批量写入缓存。预热的Key要覆盖高频访问的数据,比如首页推荐、热门商品等,避免预热Key覆盖冷门数据,浪费资源。
十四
在高并发场景下,Redis的读写性能至关重要。我亲测在负载高的时候,单节点Redis可能不够用,必须用集群或主从架构。例如,用Redis Cluster部署,每个节点存储一部分Key,负载均衡到多个节点。在配置中,需要设置合理的槽位分配和数据迁移策略,避免单点故障。另外,主从架构可以用来做读写分离,主节点负责写,从节点负责读,提高并发能力。
十五
缓存穿透和雪崩虽然表现不同,但都可以通过预热和过滤解决。我亲测在某系统中,使用布隆过滤器过滤非敏感Key,同时用本地缓存和Redis Cluster做负载分担。例如,布隆过滤器拦截无效Key,本地缓存兜底高频访问,Redis Cluster处理剩余流量。这种组合能有效避免穿透和雪崩,但需要在部署时确保所有节点都能访问布隆过滤器。如果布隆过滤器部署在单独的节点上,可能需要做额外的容灾设计。
缓存穿透击穿雪崩解决 | 容灾备份
缓存穿透、击穿、雪崩是分布式系统中极具破坏力的三种缓存问题。穿透是因为查询一个不存在的Key导致大量请求穿透到数据库,压垮后端;击穿是热点Key过期后瞬间大量并发请求涌入数据库;雪崩则是大量缓存同时失效,导致系统整体负载激增。我亲测在高并发场景下,使用Redis+Lua脚本+布隆过滤器+热点Key永不过期+本地缓存+异步刷新的组合,能有效
系统架构AI3 次阅读
Related
延伸阅读

DeepSeek V4源码解析:趋势预判 | 未来五年预判大模型资讯 · 2026-07-10

新手必看:自然语言编程工作流搭建 | 5分钟学会AI工具实战 · 2026-07-14

4个MongoDB索引SQL调优,性能提升10倍数据库 · 2026-07-14

保姆级教程 | PostgreSQL优化:性能优化实战数据库 · 2026-07-10

Tabnine配置优化:20个必备技巧AI工具实战 · 2026-07-11

建议收藏:VS Code Cursor 性能优化 | 老用户总结VS Code指南 · 2026-07-10