原创

Redis架构师方案—聊聊缓存穿透、缓存击穿、缓存雪崩及解法

1. 缓存防护失效

缓存的作用主要是拦截部分流量,防止打到DB,造成DB过大压力,最终不可用。而我们熟知的穿透、击穿、雪崩等问题,本质上就是cache层本应该拦截的一些流量没拦成功,打到DB了。即,缓存防护失效。

使用缓存的通用流程图如下:

alt

2. 缓存穿透

描述

缓存穿透指的是访问redis中一个不存在的key的时候,导致缓存无法命中,每次请求都要穿透到数据库中进行查询,导使数据库压力过大,甚至挂掉。

解法

  • 布隆过滤器拦截。

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层数据库的查询压力。

这种方法就是缓存所有key的数据坐标到bitmap中,坐标不存在则数据肯定不存在,这样就拦截了一大部分不存在的key,但是布隆过滤是有一定的误差的,即坐标存在、数据也可能不在。

  • 缓存空对象。

另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空,仍然把这个空结果进行缓存,这意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除,一般最长不超过五分钟。

3. 缓存击穿

描述

缓存击穿,是指当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。

也就是说防护层出现一个孔,这个空漏了很大一部分流量到DB。

解法

1)设置热点数据永不过期。

“永远不过期”包含两层意思:从缓存层面来看,确实没有设置过期时间,所以不会出现热点 key 过期后产生的问题,也就是“物理”不过期。

从功能层面来看,为每个 value 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。

2)加互斥锁。

分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

本地锁:与分布式锁类似,我们通过本地锁的方式来限制只有一个线程去数据库中查询数据,而其他线程只需等待,等前面的线程查询到数据后再访问缓存。但是,这种方法只能限制一个服务节点只有一个线程去数据库中查询,如果一个服务有多个节点,则还会有多个数据库查询操作,也就是说在节点数量较多的情况下并没有完全解决缓存并发的问题。

队列:所有失效key都放到队列,由一个异步线程来消费消息,并将时间缓存到cache。

4. 缓存雪崩

缓存雪崩是指设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,导致所有的查询同一时刻都落在了数据库上,造成数据库压力过大。

解决方案:

1)不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

2)在缓存失效后,通过加分布式锁或者分布式队列的方式来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

alt

正文到此结束
本文目录