Redis——缓存击穿问题

Redis——缓存击穿问题

1、概述

2、解决方法。

2.1 互斥锁

2.1 自动续期

2.2 逻辑过期

3、总结


1、概述

        如果说缓存穿透是用不存在的数据打穿防线,那缓存击穿就是盯着一个热点数据打穿防线。

        举个最常见的例子:电商平台的秒杀商品 —— 比如一款限量 1000 件的低价手机,商品 ID=1001,这个 Key 会被百万用户频繁查询,是典型的热点数据,缓存里一直存着它的信息,数据库几乎不用处理这个商品的查询请求。

        但缓存里的数据总有过期时间(TTL)。假设凌晨 2 点这个 Key 刚好过期,而此时恰好有 10 万用户同时刷新商品详情页—— 这 10 万请求会同时发现缓存里没有商品 1001 的数据,瞬间全部涌向数据库,请求查询同一条数据。

        数据库原本对这个商品的查询压力几乎为 0,突然要承接 10 万并发查询,极有可能瞬间 CPU 飙升到 100%、连接数耗尽,哪怕只是持续 1 秒,也可能导致数据库临时不可用,进而引发依赖该数据库的其他接口连锁故障 —— 这就是缓存击穿:单个热点数据缓存过期瞬间,大量并发请求穿透缓存,集中冲击数据库。

        它和缓存穿透的核心区别在于:穿透是查不存在的数据,击穿是查存在但缓存刚好过期的数据;穿透的请求是分散的随机 Key,击穿的请求是集中的同一热点 Key

2、解决方法。

2.1 互斥锁

        既然问题出在大量请求同时查库,那互斥锁的思路就很直接:给查库操作加把锁,让同一时间只有一个请求能去查数据库,其他请求要么等锁、要么直接返回缓存旧值(如果允许的话),从根源上避免数据库被蜂拥而至的请求压垮。​

        这种方法的适用场景很广,尤其是热点 Key 数量不固定、偶尔会新增的场景 —— 比如电商大促时,突然火起来的网红单品,事前没法确定它是热点,但用互斥锁能临时顶住压力。​

        优点:不用提前预判热点 Key,通用性强;而且缓存更新是实时的,能最大程度保证缓存与数据库数据一致缺点:加锁和等待的过程会增加请求的响应时间,如果锁没做好释放,还可能导致死锁,得给锁加个过期时间来规避。

2.1 自动续期

       缓存自动续期的核心思路是变被动等待过期为主动提前更新:不再依赖缓存的 TTL(过期时间)被动失效,而是在业务层单独部署定时任务(如基于 XXL-Job、Quartz 等调度框架),按预设频率从数据库拉取热点 Key 的最新数据,主动覆盖缓存中的旧值。这样,用户请求永远命中缓存,没过期窗口期,自然不会发生缓存击穿。

        这种方法适合热点 Key 提前明确、数量较少的场景 —— 比如电商首页固定展示的Banner 图(循环轮播的广告图片 / 宣传图)、秒杀商品。这类场景的热点 Key 不会频繁新增或变更,用定时任务维护缓存更新成本低、效果稳定。

        优点:实现简单,完全杜绝击穿风险;缺点:只适合热点 Key 明确且数量少的场景,如果热点 Key 太多,定时任务会频繁查询数据库,反而增加数据库压力;且数据更新有延迟,可能出现缓存与数据库不一致的短暂问题。

2.2 逻辑过期

        逻辑过期指的是它不让缓存真的过期,也就是不设置 TTL,而是在缓存的 Value 里加一个逻辑过期时间字段,例如:

{
  "product_info": {
    "product_id": 1001,
    "product_name": "智能手机",
    "product_price": 2999.00,
    "stock_quantity": 867,
    "is_seckill": true
  },
  "logical_expire_time": "2024-05-20 02:00:00"
}

具体流程是这样的:​

        用户请求查商品 1001,先读缓存,发现缓存存在;​检查缓存里的逻辑过期时间:如果没过期,直接返回商品信息;​如果逻辑过期了,不立刻删除缓存,而是启动一个异步线程(比如用线程池)去数据库查最新数据、更新缓存(包括更新逻辑过期时间);​与此同时,当前请求不等待异步线程的结果,直接返回缓存里的旧数据。​

        这种方法的核心是用旧数据换并发安全—— 即使缓存逻辑过期了,也先给用户返回旧数据,再偷偷异步更新缓存,既避免了请求涌向数据库,又保证了用户体验(不用等)。​

        适合什么场景?对数据实时性要求不高,但对并发和响应速度要求高的场景 —— 比如电商商品的浏览量、收藏数、新闻资讯的列表页。​

        优点:并发能力极强,没有锁等待,响应速度快;缺点:会返回旧数据,不适合实时性要求高的场景(比如秒杀商品的库存,旧数据可能导致超卖);而且缓存永远不删,会占用更多内存,需要定期清理无效缓存。        

3、总结

        缓存击穿的本质是热点数据过期瞬间的并发查库,解决思路围绕避免大量请求同时查库展开,不同方案各有适配场景,实际开发中要按需选择:​

  • 若热点 Key 提前明确、数量少 → 选「自动续期」,简单高效,零击穿风险;​
  • 若热点 Key 不固定、偶尔新增,且要求数据实时性 → 选「互斥锁」,通用性强,数据一致;​
  • 若对并发和响应速度要求高,对数据实时性要求低 → 选「逻辑过期」,并发拉满,用户体验好。​

        实际项目中不用局限于单一方案,可以把互斥锁和逻辑过期结合 —— 逻辑过期时,先返回旧数据,再用互斥锁保证只有一个异步线程去查库,既避免并发查库,又防止多个异步线程重复更新缓存,性价比更高。

### Redis 缓存击穿的原因 缓存击穿是指在高并发情况下,大量请求访问同一个 key,在该 key 正好过期的时候,这些请求会穿透缓存直接打到数据库上,造成数据库压力骤增甚至崩溃的情况[^1]。 由于缓存中的数据具有时效性,通常会对存储的数据设定一个合理的过期时间。一旦到达这个时间点,缓存将不再保留这条记录,此时如果多个客户端几乎同时发起对该条目对应的资源请求,则可能导致上述现象的发生[^2]。 ### 解决办法 #### 方法一:逻辑过期机制 为了避免因物理删除而导致的瞬时热点问题,可以引入逻辑上的“软删除”。即给每一条缓存增加额外的有效期限字段(比如 TTL),即使实际的缓存已经达到了预设的最大存活周期而被标记为可回收状态,但在一定时间内仍然允许读取操作成功返回旧版本的内容;只有当真正超过此附加有效期之后才会彻底移除该项,并触发一次完整的加载流程来更新最新值[^3]。 ```java // Java 实现逻辑过期伪代码 public class CacheItem { private Object value; private long expireAt; // 绝对过期时间戳 public boolean isExpired() { return System.currentTimeMillis() >= this.expireAt; } } ``` #### 方法二:互斥锁控制 通过分布式环境下的唯一标识符——如基于 Redis 自身实现简单的信号量/红绿灯模型——使得同一时刻只有一个进程能够执行特定的任务序列,从而有效防止了多实例竞争条件下可能引发的一系列连锁反应。具体来说就是在尝试获取某项不存在于本地副本里的对象之前先去争抢一把全局唯一的令牌,拿到手后再继续往下走;反之则需耐心排队等候直至轮到自己为止[^4]。 ```java // Java 实现互斥锁示例代码 String lockKey = "mutex_lock"; try (Jedis jedis = new Jedis()) { while (!jedis.setnx(lockKey, "locked").equals(1L)) { Thread.sleep(random.nextInt(10)); // 随机休眠减少冲突概率 } try { // 执行缓存未命中的处理逻辑... } finally { jedis.del(lockKey); // 清理工作务必放在finally里确保释放 } } ``` #### 方法三:布隆过滤器前置校验 利用布隆过滤器这种空间效率极高的随机化算法结构作为第一道防线,可以在正式查询前快速判断目标是否存在可能性。对于那些肯定不在集合内的询问可以直接拒绝掉,进而减轻后续环节的工作负担。不过需要注意的是这种方法存在一定误判率,因此只适用于容忍少量假阳性的业务场景下使用。 ```go package main import ( "fmt" bloom "github.com/steakknife/bloomfilter" ) func initBloomFilter(size int) *bloom.BloomFilter { return bloom.NewWithEstimates(float64(size), 0.01) } func checkExistence(filter *bloom.BloomFilter, item string) bool { return filter.Test([]byte(item)) } func addNewItem(filter *bloom.BloomFilter, item string) { filter.Add([]byte(item)) } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值