文章目录
0. 前言
Redis 是后端开发中最常用的缓存中间件,几乎每个项目都会使用 Redis ,所以 Redis 经常会在面试题中出现
在与 Redis 相关的面试题中,缓存穿透、缓存雪崩、缓存击穿
这三个问题经常一起出现,所以这三个问题也被大家称为 Redis 缓存面试三兄弟
本文将为大家分析缓存穿透、缓存雪崩、缓存击穿
产生的原因以及常见的解决方案
1. 缓存穿透
1.1 什么是缓存穿透
缓存穿透是指在高并发的情况下,客户端请求的数据在缓存中不存在,在数据库中也不存在,导致每次请求都直接打到数据库上,增大数据库的压力,甚至导致数据库服务不可用
1.2 缓存穿透产生的原因
1.2.1 恶意攻击
假设现在后台服务器有一个根据文章 id 获取文章详情的接口
/api/article/getDetail?id=1
如果黑客知道了接口的编写规则,就可以伪造假请求来冲击你的接口(比如用 id 等于 -1、id 等于 0 或者一些很大的 id 值来伪造假请求)
1.2.2 业务逻辑错误
由于业务逻辑的漏洞或者数据同步问题,导致某些应该存在的数据实际上并不存在
1.3 缓存穿透的解决方案
1.3.1 方案一:参数校验(需要与其它方案结合使用)
在接口层面进行参数校验,过滤掉非法请求或不合理的请求
1.3.2 方案二:缓存空值
对于查询结果为空的 key ,在缓存中存储一个特殊的空值(例如:{ key: null }
),并设置较短的过期时间,这样后续的相同请求就会直接命中缓存,不会访问数据库,从而减轻数据库的压力
1.3.2.1 优点
逻辑简单,容易实现
1.3.2.2 缺点
- 数据不一致性:如果数据库中的数据发生变化(例如,原本不存在的数据现在被创建了),缓存中的空值不会自动更新,可能会导致短暂的数据不一致
- 过期时间设置困难:确定合适的过期时间比较困难,太短可能导致缓存穿透问题仍然存在,太长则可能导致数据不一致的问题
- 占用内存:当有多个 key 查询结果为空时,会占用一定的内存
1.3.3 方案三:使用布隆过滤器
使用布隆过滤器之后,整体的业务流程如下
1.3.3.1 什么是布隆过滤器
布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,由一个只存储 0 和 1 的数组和若干个哈希函数组成
布隆过滤器可以用来快速判断一个元素是否可能存在于某一个集合中
1.3.3.2 布隆过滤器的原理
在了解布隆过滤器的原理之前,我们先来了解一下 bit map (位图)
bit map 可以看做是一个以 bit 为单位的数组,数组的每个单元只存储 0 或 1 ,初始值都是 0
布隆过滤器的实现依赖于 bit map
当我们往数据库中写入数据(将数据写入数据库之后之后也会写入 Redis 缓存),会在布隆过滤器里做标记
下一次判断数据是否在数据库时,只需要通过布隆过滤器进行判断,如果数据在布隆过滤器没有被标记,说明该数据在数据库中不存在
布隆过滤器通过 3 个操作完成标记:
- 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值
- 第二步,将第一步得到的 N 个哈希值对 bit map 的长度取模,得到每个哈希值在 bit map 中的对应位置
- 第三步,将每个哈希值在 bit map 中的对应位置的值设置为 1
举个例子,假设有一个 bit map 长度为 16 ,哈希函数个数为 3 的布隆过滤器,现在要往数据库中插入一条 id 为 1 的数据
这条 id 为 1 的数据会被 3 个哈希函数分别计算出 3 个哈希值,然后将 3 个哈希值对 16 取模,假设取模的结果为 1、3、7,那么 bit map 的第 1、3、7 个位置的值就会被设置为 1
当要判断 id 为 1 的数据
是否存在于数据库时,只需要通过布隆过滤器判断 bit map 的第 1、3、7 位置的值是否全为 1
只要 1、3、7 中有任何一个位置为 0,就可以认为 id 为 1 的数据
在数据库中不存在
布隆过滤器是基于哈希函数实现判断的的,存在哈希冲突的可能性,也就是说,使用布隆过滤器可能会出现误判的情况
下面是一个误判的例子
总结:
- 如果布隆过滤器判断数据不存在,那么数据库中一定没有这个数据
- 如果布隆过滤器判断数据不存在,数据库中不一定存在这个数据,有误判的几率
那么如何降低误判的几率呢
根据布隆过滤器的原理,我们可以知道
- bit map 的长度越小,误判几率越大
- bit map 的长度越大,误判几率越小
如果 bit map 的长度过小,很容易出现误判的情况;如果 bit map 的长度很大,内存开销也会增大
当然,这种造轮子的工作已经有前辈为我们做好了,比如 Redission 、Guava 等框架都提供了布隆过滤器的具体实现
1.3.3.3 优点
内存占用少、没有多余的 key
1.3.3.4 缺点
实现复杂、可能存在误判的情况
1.3.4 方案四:给业务添加降级限流策略(保底方案)
当系统扛不住较高的并发时,可以给业务添加降级限流策略(常见于微服务架构)
2. 缓存击穿
2.1 什么是缓存击穿
在某个时间点,缓存中某个热点数据过期了,此时恰好有大量的请求访问该热点数据,由于该数据在缓存中已经过期了,这些并发请求会同时打到数据库上,瞬间压垮数据库,这种现象被称为缓存击穿
2.2 缓存击穿的解决方案
2.2.1 方案一:互斥锁
保证同一时间只有一个业务线程更新缓存,对于未能获取互斥锁的请求,需要等待锁释放后再重新读取缓存
2.2.1.1 优点
能够保持数据的强一致性
2.2.1.2 缺点
由于使用了互斥锁,性能会下降
2.2.2 方案二:逻辑过期
我们不给热点数据设置真实的过期时间,而是给热点数据设置一个逻辑过期时间
我们往 Redis 中存入热点数据时,可以额外添加一个字段(通常是一个时间戳,比如{"id": 1, "title": "Redis缓存面试三兄弟", "expire": 1721394558}
)来记录热点数据的过期时间
- 如果当前时间在热点数据的过期时间之前,说明热点数据还没有过期
- 如果当前时间在热点数据的过期时间之后,说明热点数据已经过期了
当热点数据过期时,先返回旧数据,再由后台异步更新缓存
2.2.2.1 优点
能扛住更高的并发量
2.2.2.2 缺点
会出现短暂的数据不一致的情况
2.2.3 方案三:给业务添加降级限流策略(保底方案)
当系统扛不住较高的并发时,可以给业务添加降级限流策略(常见于微服务架构)
2.3 选择哪种方案
- 如果业务对数据一致性要求很高,比如涉及到金钱交易的业务,首选
互斥锁
方案 - 如果业务对数据一致性要求不高,更注重用户的体验,比如互联网项目,可以选择
逻辑过期
方案
3. 缓存雪崩
3.1 什么是缓存雪崩
缓存雪崩是指在同一时段大量的 key 同时失效或者 Redis 服务宕机,导致大量请求直接打到数据库,给数据库服务器带来巨大压力
3.2 缓存雪崩的解决方案
3.2.1 方案一:给不同的 key 设置不同的过期时间
给不同的 key 设置不同的过期时间,避免大量数据同时过期
3.2.2 方案二:搭建 Redis 集群
可以搭建 Redis 集群(哨兵模式、集群模式、主从模式)来提高 Redis 缓存服务的可用性
3.2.3 方案三:给业务添加多级缓存
例如,可以使用 Caffeine 作为一级缓存, Redis 作为二级缓存
3.2.4 方案四:给业务添加降级限流策略(保底方案)
当系统扛不住较高的并发时,可以给业务添加降级限流策略(常见于微服务架构)
标签:方案,缓存,1.3,Redis,布隆,雪崩,过滤器,2.2 From: https://blog.csdn.net/m0_62128476/article/details/140580942