Redis 缓存策略与缓存穿透、雪崩、击穿的防护方案

发布时间:2026/7/8 18:12:28
Redis 缓存策略与缓存穿透、雪崩、击穿的防护方案 Redis 缓存策略与缓存穿透、雪崩、击穿的防护方案一、缓存引入的复杂性往往比它解决的性能问题更难处理Redis 几乎是每个需要性能优化的系统都会引入的组件。把它放在数据库前面读请求先查缓存缓存没有再查数据库并回写缓存——这个模式很简单也能解决大部分读密集场景的性能瓶颈。但缓存引入后系统里多了一个需要维护、监控和在高可用架构里考虑故障切换的组件。更麻烦的是缓存和数据库之间的数据一致性问题、缓存失效时的雪崩问题、以及恶意请求导致的缓存穿透问题都是引入缓存之前不存在的问题。一个好的缓存策略必须在「缓存命中率」、「数据一致性」和「系统复杂度」之间做权衡。Cache-Aside旁路缓存模式实现简单、适用场景广但一致性最弱Read-Through/Write-Through 模式一致性更好但实现更复杂Write-Behind 模式性能最好但一致性最弱适合对一致性要求不高的场景。没有哪种模式是万能的选型取决于业务对数据一致性的要求。但无论选哪种模式缓存系统都必须处理三个经典问题缓存穿透查询一个不存在的数据缓存和数据库都没有导致每次请求都打到数据库、缓存雪崩大量缓存同时失效导致数据库压力骤增、缓存击穿一个热点 Key 失效的瞬间大量请求打到数据库。这三个问题都有成熟的解决方案但需要在设计缓存层时就考虑进去而不是等问题出现再补救。二、缓存策略选型Cache-Aside、Read-Through 与 Write-Behind 的权衡flowchart TD A[缓存策略选型] -- B{一致性要求?} B -- 最终一致可接受 -- C[Cache-Aside] B -- 较高一致性 -- D[Read-Through / Write-Through] B -- 性能优先/可丢失 -- E[Write-Behind] C -- F[实现简单/需手动管理] D -- G[一致性好/实现复杂] E -- H[性能最好/可能丢数据] F -- I[适合读多写少] G -- J[适合一致性敏感] H -- K[适合点选/计数]Cache-Aside旁路缓存是最常用的模式。读流程先查缓存命中则返回未命中则查数据库结果写入缓存返回。写流程先更新数据库再删除缓存注意是删除而不是更新缓存这是 Cache-Aside 的标准做法。删除缓存而不是更新缓存的原因是并发情况下更新缓存的操作可能覆盖成旧值。但 Cache-Aside 有一个一致性窗口在「删除缓存」和「下一个读请求把新数据加载到缓存」之间如果有读请求会读到旧数据。这个窗口通常很短毫秒级对大多数业务是可接受的。如果对一致性要求更高可以考虑在写操作完成后延迟一小段时间再删除一次缓存延迟双删或者用消息队列保证缓存删除的可靠性。Read-Through 和 Write-Through 模式把缓存当成一个「数据访问层」应用代码只和缓存交互缓存负责和数据库同步。这种模式的一致性更好因为缓存层可以保证「读一定走缓存、写一定同时写缓存和数据库」。但它的实现更复杂通常需要用一个支持这种模式的缓存客户端或者库。Write-Behind异步写回模式是性能最好的写操作只写缓存不立即写数据库而是定期批量写回数据库。这个模式的性能非常好因为写操作不需要等数据库确认但一致性最差如果缓存节点故障还没写回数据库的数据就丢了。它适合对一致性要求不高、但写吞吐要求很高的场景如计数器、点赞数。三、缓存穿透、雪崩与击穿问题分析与工程解决方案缓存穿透是指查询一个缓存和数据库都没有的数据。攻击者可以构造大量不存在的 ID 发送请求绕过缓存直接打数据库导致数据库压力骤增。解决方案有两种缓存空值和布隆过滤器。缓存空值如果数据库查不到也在缓存里写一个空值如NULL并设置一个较短的过期时间如 5 分钟。这样下次同样的请求就能命中缓存不会再打到数据库。但这种方案不能防止「每次用不同的不存在 ID 攻击」——因为每次 ID 不同缓存里也没有。布隆过滤器在缓存前面加一个布隆过滤器它能在常数时间内判断「某个 ID 是否可能存在」。如果布隆过滤器说「不存在」那一定不存在可以直接返回如果布隆过滤器说「可能存在」才继续走缓存和数据库的查询流程。布隆过滤器的缺点是「有误判率」可能存在但实际不存在以及「不支持删除」。Redis 4.0 的 RedisBloom 模块提供了布隆过滤器的实现。缓存雪崩是指大量缓存同时失效导致所有请求都打到数据库。解决方案是「过期时间加随机值」每个缓存的过期时间 基础过期时间 随机偏移量如 300 秒 random(0, 60) 秒。这样缓存的失效时间会分散开不会同时失效。另一个雪崩的解决方案是「缓存永不过期 后台更新」缓存不设过期时间由后台任务定期更新缓存。这种方式的好处是缓存永远不会失效坏处是数据可能不是最新的需要实现更新机制。缓存击穿是指一个热点 Key 在失效的瞬间大量并发请求同时发现缓存未命中同时去查数据库导致数据库压力骤增。解决方案是「互斥锁」当缓存未命中时只有一个请求可以去查数据库其他请求等待这个结果。在 Redis 里可以用SET key value NX EX timeout实现简单的分布式锁第一个请求设置锁成功去查数据库并回写缓存后续请求设置锁失败等待一小段时间后重试读取缓存。四、生产环境缓存管理内存淘汰策略、监控与缓存预热Redis 的内存是有限的当内存满了之后需要有策略决定删除哪些缓存。Redis 提供了多种内存淘汰策略maxmemory-policy包括noeviction不淘汰写操作时返回错误默认allkeys-lru所有 Key 里删除最久未使用的 Keyallkeys-random所有 Key 里随机删除volatile-lru设置了过期时间的 Key 里删除最久未使用的volatile-random设置了过期时间的 Key 里随机删除volatile-ttl设置了过期时间的 Key 里删除 TTL 最短的生产环境通常选择allkeys-lru或者volatile-lru。但 LRU 是近似 LRU不是精确 LRU——Redis 为了性能采样少量 Key 然后淘汰其中最久未使用的不是扫描所有 Key。缓存监控的关键指标包括缓存命中率hit rate、内存使用量、连接数、命令延迟、以及 evicted_keys被淘汰的 Key 数量。命中率低于 80% 通常意味着缓存策略有问题如缓存过期太快、缓存 Key 设计不合理evicted_keys 持续大于 0 意味着内存不够需要考虑扩容或者优化缓存策略。缓存预热是系统重启或者新上线时的一个重要操作。如果系统重启后缓存是空的所有请求都会打到数据库可能把数据库打挂。缓存预热的做法是在系统启动后、接收流量前先把热点数据加载到缓存里。可以写一个预热脚本也可以在应用启动逻辑里触发。五、总结Redis 缓存策略的设计是在缓存命中率、数据一致性和系统复杂度之间做权衡。Cache-Aside 模式实现简单适合大多数场景缓存穿透用布隆过滤器或者缓存空值来解决缓存雪崩用过期时间加随机值来防止缓存击穿用互斥锁来保护数据库。生产环境中内存淘汰策略的选择、缓存命中率的监控、以及缓存预热的准备是把缓存从「能跑」变成「能稳定跑」的三个关键工程细节。缓存不是银弹但不合理地使用缓存比不使用缓存更危险。