缓存穿透、缓存击穿和缓存雪崩详解

TrumanWong
4/7/2022
TrumanWong

缓存穿透

首先我们来了解下面这个基本操作:

graph LR
A[客户端] --> B(业务服务)
B --> C(MySQL服务)

当客户端发送数据请求的时候,服务业务会执行MySQL数据库或者将结果返回给客户端,当然这在并发量少的时候没有任何问题。如果并发量比较大,每一次请求数据都要操作MySQL数据库,那么就需要执行阻塞的IO操作。最简单、最廉价的方法就是缓存,此时操作流程就会演变成如下图所示的业务架构图:

graph TD
A[客户端] --> B[业务服务]
B --> C{数据是否在缓存中}
C --> |是| B
C --> |不存在| D(查询数据库)
D --> |更新缓存数据|C

需要注意的是:当查询Redis中没有数据时,该查询会下沉到数据库层,同时数据库层也没有该数据,当出现大量这种查询(或被恶意攻击)时,接口的访问全部透过Redis访问数据库,而数据库中也没有这些数据,我们称这种现象为“缓存穿透”。缓存穿透会穿透Redis的保护,让底层数据库的负载压力变大。

如果大量请求查询的数据既不在缓存中,也不在MySQL数据库中,那么会造成每次的查询操作都会对数据库进行查询,因为不存在的数据是没有办法缓存的,于是就造成MySQL数据库承受高并发的查询请求,如下图所示:

img

针对上面的问题,这里列出两个解决方案:

在数据访问的第一层我们采用布隆过滤器过滤,如下图所示,虽然不能完全避免数据穿透的现象,但布隆过滤器已经可以将99.99%的穿透查询屏蔽在Redis曾,极大地降低了底层数据库的压力,减少了资源浪费。

img

我们可以把布隆过滤器的层级放到缓存下一层,当请求数据的时候先去缓存层获取。如果缓存里面有数据,就直接返回结果;如果缓存没有,就去布隆过滤器判断相关数据的键是否在布隆过滤器中,如果在就继续查询MySQL数据库,如果不存在就直接返回,不再进行数据库层级的查询。

缓存击穿

缓存击穿和缓存穿透从名词上很难区分开,它们的区别主要是**“穿透”表示底层数据库没有数据而且缓存层也没有数据,而“击穿”表示底层数据库有数据但是缓存层没有数据**。当热点数据的键从缓存中淘汰出去后,大量访问同时请求这个数据就会将查询下沉到数据库层,此时数据库层的负载压力增大,我们称这种现象为缓存击穿

graph TD
A(客户端1) --> B[Redis]
C(客户端2) --> B
D(客户端3) --> B
B --> E
B --> |当前缓存数据过期|E[MySQL]
B --> E

针对上面的问题,这里列出两种解决方案:

缓存雪崩

缓存击穿说的是热点数据过期,缓存雪崩说的是大面积的数据过期。缓存雪崩是指Redis中大量的键几乎同时过期,然后大量并发的查询穿过Redis冲击到底层数据库上,此时数据库层的负载压力会增大。相比于缓存击穿,缓存雪崩更容易发生。如下图所示,大量缓存同一时间过期造成了缓存雪崩问题:

graph TD
A(客户端1) --> D[Redis]
B(客户端2) --> |请求的key在同一时间都失效了|D
C(客户端2) --> D
D --> E[MySQL]
D --> |去数据库查询数据|E
D --> E

针对上面的问题,这里列出两种解决方案: