redis缓存穿透和雪崩
前言
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题,就是数据的一致性问题,从严格意义上来说,这个问题无解。如果对数据一致性要求很高,那么就不能使用缓存。
另外的一些典型问题就是,缓存穿透、缓存击穿和缓存雪崩。
一、缓存穿透
概念
缓存穿透指的是访问redis中的一个不存在的key时,导致缓存无法命中,每次请求都要穿透到数据库中进行查询,导致数据库压力过大,甚至挂掉。缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库带来很大的压力,这时候就相当于出现了缓存穿透。
解决方案
1. 布隆过滤器
有很多方法有效地可以解决缓存穿透问题,最常见的则是采用布隆过滤器,就是讲所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截,从而避免了对持久层数据库的查询压力。
布隆过滤器是一种数据结构,据统计垃圾网站和正常网站加起来全世界也有十几亿。网警要过滤这些垃圾网站,总不能导数据库中一个一个的去比较吧,这就可以使用布隆过滤器。假设我们存储一亿个网站地址。
可以先有一亿个二进制比特,然后网警用8个不同的随机数产生器(F1-F8)产生8个信息指纹(f1-f8)。接下来有一个随机数产生器G把这8个信息指纹映射到1到1亿中的8个自然数g1-g8,最后把这8个位置的二进制全部设置为1,过程如下:
有一天网警查到了一个可疑的网站,相判断一下是否是xx网站,首先将可疑的网站映射到1亿个比特数组上的8个点,如果8个点的其中一个点不为1,则可以判断该元素一定不存在集合中。
那这个布隆过滤器是如何解决redis的缓存穿透问题的呢?很简单,首先也是对所有可能查询到的参数以hash形式存储,当用户想要查询的时候,使用布隆过滤器发现不在集合中,接直接丢弃,不在对持久层查询。
2. 缓存空对象
另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空,仍然把这个空结果进行缓存,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。
但是这种方法会存在两个问题:
- 如果控制被缓存起来,这意味着缓存层中存了更多的key,需要更多的内存空间;
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对需要保持一致性的业务会有影响。
二、缓存击穿(缓存并发)
概念
缓存击穿,是指当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问持久层数据库来查询数据,并且写回缓存,会导致持久层数据库瞬间压力过大。
解决方案
1. 设置热点数据永不过期
“永不过期”包含两层意思:从缓存层面,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
2. 加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
本地锁:与分布式锁类似,我们通过本地锁的方式来限制只有一个线程去持久层数据库中查询数据,而其他线程只需等待,等前面的线程查询到数据后再访问缓存。但是,这种方式只能限制一个服务节点只有一个线程去持久层数据库中查询,如果一个服务有多个节点,则还会有多个持久层数据库查询操作,也就是说在节点数量较多的情况下并没有完全解决缓存并发的问题。
三、缓存雪崩
概念
缓存雪崩是指缓存层出现了问题,不能正常工作了或者在某一时间段,缓存集中过期,或者数据数据未加载到缓存中等情况下,导致所有的请求直接到达存储层,存储层的调用量会暴增,造成存储层也会挂掉。
解决方案
1. redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis服务器,这样一台挂掉之后其他的还可以继续工作,其实就是搭建集群。
2. 限流降级
这个解决方案的思路是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询持久层数据库和写缓存,其他线程等待。
3. 数据预热
数据预热的含义就是在正式部署之前,我先把可能访问到的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。