缓存旁路模式 (Cache-Aside Pattern)
-
- 读数据(先查缓存,再查数据库)
-
- 写/更新数据(先改数据库,再删缓存)
缓存穿透 (Cache Penetration):
-
现象:有人故意疯狂请求一些根本不存在的数据(比如随机生成的负数 ID)。因为数据不存在,Redis 里没有,MySQL 里也没有。每次请求都会穿透缓存,直接把数据库打垮。
-
解决:使用布隆过滤器,或者把这些“压根不存在的数据”也在 Redis 里存一个空值(设置较短的过期时间),挡住恶意请求。
缓存雪崩 (Cache Avalanche):
现象:大量缓存在同一时间集体过期失效。那一瞬间,海量的请求全部涌向了数据库,导致数据库压力骤增甚至宕机。 解决:在给缓存设置过期时间时,加上一个随机的偏移量(比如在 30 分钟的基础上,随机加 1-5 分钟),让缓存的失效时间错开。
缓存击穿 (Cache Breakdown):
-
现象:某一个极度热点的数据(比如秒杀活动的商品详情)突然过期了。在这一瞬间,成千上万的请求同时去查这个 Key,导致数据库瞬时压力过大。
-
解决:使用互斥锁(Mutex Lock)。当发现热点 Key 失效时,只让第一个请求去查数据库并重建缓存,其他请求乖乖排队等待,等缓存建好后直接从 Redis 读取。
什么是延迟双删?
它的核心思路很简单:既然怕中间有人把旧数据塞回缓存,那我就多删一次!具体步骤如下:
- 先更新数据库:把商品价格改成 99 元。
- 第一次删除缓存:先把 Redis 里的旧缓存删掉。
- 休眠一小段时间:比如睡个 500 毫秒(这个时间需要根据实际业务估算)。
- 第二次删除缓存:再次去删除 Redis 里的缓存。
为什么要这么做?
这第二次删除,就是为了清理那些在你第一次删完缓存后、还没来得及更新数据库时,偷偷溜进来把“旧价格”重新写回 Redis 的请求。等这几百毫秒过去,数据库早就更新完了,这时候再把缓存一删,后续的用户来查,就只能乖乖去数据库拉取最新的 99 元价格了。
另一个隐患:如果删除缓存失败了怎么办?
除了并发导致的不一致,还有一个更致命的情况:假如网络抖了一下,或者 Redis 突然卡顿了,导致你的代码根本没删掉缓存,那这个旧价格就会一直留在缓存里,直到过期(可能是几小时甚至几天)
- 解决:
- 加重试机制:删缓存失败时,不要只打个日志就完事。可以循环重试几次(比如重试 3 次),确保尽量删成功。
- 消息队列(MQ)兜底:如果重试了好几次还是删不掉,就把这个缓存的 Key 发送到消息队列(如 Kafka、RocketMQ)里。让一个专门的后台服务去监听这个队列,异步地、坚持不懈地帮你把这个缓存删掉。