吃透理解Redis缓存三兄弟:缓存穿透、缓存击穿、缓存雪崩

Redis相关必问面试题之三种业务场景的问答、完全吃透理解!

目录

Q1:什么是缓存穿透?怎么解决?

Q2:你能介绍一下“布隆过滤器”吗?

布隆过滤器拓展

如何在有限的空间内表示一个可能非常大的集合,并快速判断某个元素是否“可能”存在?

为什么使用较小的空间?

为什么需要多次哈希?

为什么这样能降低误判率?

情景示意图

Q3:什么是缓存击穿?怎么解决?

解决方案

方案一:互斥锁(强一致)

方案二:逻辑过期(高可用)

可能的追问/回答

Q4:什么是缓存雪崩?怎么解决?

解决方案

缓存穿透、缓存击穿和缓存雪崩的横向对比理解


Q1:什么是缓存穿透怎么解决?

缓存穿透是指查询一个一定不存在的数据,由于存储层查不到数据因此不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。

这种情况大概率是遭到了攻击。

解决方案的话,我们通常都会用布隆过滤器来解决它。


Q2:你能介绍一下“布隆过滤器”吗?

布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是Redisson实现的布隆过滤器。它的底层原理是,先初始化一个比较大的数组(BitMap),里面存放的是二进制0或1。

一开始都是0,当一个key来了之后,经过3次hash计算,模数组长度找到数据的下标,然后把数组中原来的0改为1。这样,三个数组的位置就能标明一个key的存在。查找的过程也是一样的。

当然,布隆过滤器有可能会产生一定的误判,我们一般可以设置这个误判率,大概不会超过5%。其实这个误判是必然存在的,要不就得增加数组的长度。5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。

布隆过滤器拓展

如何在有限的空间内表示一个可能非常大的集合,并快速判断某个元素是否“可能”存在?

布隆过滤器的设计初衷就是为了解决这个问题。它在空间效率查询准确性之间做出了权衡。它牺牲了100%的准确性(存在误判,但不会漏判),换取了极高的空间效率。

为什么使用较小的空间?

现实中我们不可能为每一个可能的 Long 值(资源id值)分配一个比特位。布隆过滤器使用的“较小空间”指的是一个固定大小的位数组(Bit Array),这个大小通常远小于要表示的集合中元素的数量。

为什么需要多次哈希?

这是布隆过滤器的精髓所在,也是为了在有限空间内降低误判率的关键。

假设我们有一个大小为 m 的位数组,以及要表示的 n 个元素。如果我们只用一个哈希函数 h(key),将 h(key) % m 对应的位设置为 1。

  • 当插入一个元素 key1 时,计算 h(key1) % m,将对应位设置为 1。
  • 当查询一个元素 key2 时,计算 h(key2) % m。如果对应位是 1,我们就认为 key2 可能存在。

问题来了: 即使 key2 不存在,如果存在另一个元素 key3,它的哈希值 h(key3) 经过取模后与 h(key2) 相同(即发生哈希冲突),那么 key2 的查询也会返回“可能存在”,这就发生了误判。

为了降低误判率,布隆过滤器使用了多个(k 个)独立的哈希函数。

当插入一个元素 key 时,会计算 k 个哈希值:h1(key), h2(key), ..., hk(key)。然后将 h1(key) % m, h2(key) % m, ..., hk(key) % m 对应的 k 个位全部设置为 1。

当查询一个元素 key 时,同样计算 k 个哈希值。只有当这 k 个哈希值经过取模后对应的 k 个位全部都为 1 时,我们才认为 key 可能存在。

为什么这样能降低误判率?

误判发生的条件是:一个不存在的元素 key,它的 k 个哈希值经过取模后对应的 k 个位全部都被其他存在的元素设置为了 1。

如果只用一个哈希函数,误判的概率取决于哈希冲突的概率。 如果使用多个哈希函数,误判的概率取决于这 k 个位置同时被其他元素设置的概率。这个概率要比单个位置被设置的概率低得多。

情景示意图

位数组 (m 个比特位)
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... ]

插入元素 "apple":
哈希函数1: h1("apple") % m = 2
哈希函数2: h2("apple") % m = 5
哈希函数3: h3("apple") % m = 8

位数组变化:
[ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, ... ]

查询元素 "banana":
哈希函数1: h1("banana") % m = 1
哈希函数2: h2("banana") % m = 4
哈希函数3: h3("banana") % m = 7

检查位数组:
位置 1: 0
位置 4: 0
位置 7: 0

结论: "banana" 不存在 (因为至少有一个位置是 0)

查询元素 "appla" (一个不存在的相似词):
哈希函数1: h1("appla") % m = 2  (可能是哈希冲突)
哈希函数2: h2("appla") % m = 5  (可能是哈希冲突)
哈希函数3: h3("appla") % m = 9

检查位数组:
位置 2: 1
位置 5: 1
位置 9: 0

结论: "appla" 不存在 (因为位置 9 是 0)

查询元素 "orange" (一个不存在的词,但哈希值巧合):
假设 h1("orange") % m = 2, h2("orange") % m = 5, h3("orange") % m = 8
检查位数组:
位置 2: 1
位置 5: 1
位置 8: 1

结论: "orange" 可能存在 (发生了误判)

Q3:什么是缓存击穿?怎么解决?

缓存击穿:当设置了过期时间的 key 在缓存失效的瞬间,遭遇大量并发请求,导致请求直接穿透到数据库,可能引发 DB 过载宕机。

解决方案

方案一:互斥锁(强一致)
  1. 请求发现缓存失效 → 尝试用 SETNX 获取分布式锁(SET lock_key 1 EX 10 NX
  2. 获锁线程:查询 DB → 回填缓存 → 释放锁
  3. 未获锁线程:睡眠重试或直接返回旧值(若允许短暂不一致)
    优点:保证数据强一致
    缺点:性能较低、需处理锁超时(防死锁)
方案二:逻辑过期(高可用)
  1. 数据存入缓存时:
    {  
        "value": "真实数据",  
        "expire_ts": 1672500000 // 逻辑过期时间戳
    }
  2. 请求读缓存时:
    • 若 expire_ts > now → 直接返回数据 
    • 若 expire_ts <= now → 立即返回旧数据,并尝试抢锁
      • 抢锁成功者:启动异步线程更新缓存
      • 其他请求:不阻塞,直接返回旧数据
        优点:高并发下仍可用
        缺点:存在短暂数据不一致(异步更新完成前返回脏数据)

可能的追问/回答

  1. Q:逻辑过期方案中,如果异步更新失败怎么办?
    A:可加入重试机制 + 告警监控,同时设置二次过期时间(如更新失败后5秒再过期),避免脏数据永久残留。

  2. Q:互斥锁方案中大量线程等待如何处理?
    A:引入锁等待超时机制(如线程最多等待100ms),超时后降级返回兜底数据或错误码,避免雪崩。

  3. Q:两种方案如何选择?
    A:金融/交易类用方案一(强一致),资讯/商品详情用方案二(高并发优先)。可结合业务容忍度选择。


Q4:什么是缓存雪崩?怎么解决?

指大量缓存 key 因设置了相同或相近的过期时间,在某一时刻同时失效,导致所有请求穿透到数据库,造成 DB 瞬时压力过大甚至宕机。

它与缓存击穿的区别在于,雪崩影响的是大量不同的 key,而击穿是针对某个热点 key

解决方案

  1. 设置随机过期时间:在原始过期时间基础上增加一个较小的随机值(如 1-5 分钟),使 key 的失效时间点分散开。
  2. 构建多级缓存体系:结合本地缓存(如 Caffeine)和分布式缓存(如 Redis)。本地缓存可以拦截部分请求,降低对 Redis 的压力。但需注意多级缓存一致性问题,通常通过设置 TTL 或消息通知来解决。
  3. 使用 Redis 集群:虽然不能直接解决单个 key 的击穿,但集群通过分片主从复制提高了整体的可用性和容量,能更好地应对部分节点的失效,但不能完全避免所有 key 同时失效的情况。
  4. 添加降级和限流策略
    • 降级:当 DB 压力过大时,牺牲非核心功能,如返回兜底数据、旧数据,或关闭部分功能,保障核心服务可用。
    • 限流:在缓存层前或服务入口,限制穿透到 DB 的请求数量,防止 DB 被压垮。

缓存穿透、缓存击穿和缓存雪崩的横向对比理解

问题核心概念(一句话)触发条件(一句话)影响范围(一句话)核心解决方案(一句话)
缓存穿透查询不存在数据,导致请求直达DB。大量请求查询不存在的Key针对不存在的Key的请求布隆过滤器(拦截不存在的Key)或缓存空结果(避免重复查DB)
缓存击穿热点Key过期瞬间,大量请求直达DB。单个热点Key过期 + 高并发针对单个热点Key的请求互斥锁(同步更新)或逻辑过期+异步更新(返回旧数据)
缓存雪崩大量Key同时过期,导致所有请求直达DB。大量Key同一时刻过期针对大量Key的请求分散过期时间 + 多级缓存 + 限流降级

关系图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值