缓存穿透、缓存并发和缓存雪崩是常见的由于并发量大而导致的缓存问题。这里记录下其产生原因和解决方案。
缓存穿透是由恶意攻击或无意造成的;缓存并发是由设计不足造成的;缓存雪崩是由缓存同时失效造成的。
一、缓存穿透
概念:
缓存穿透指的是使用不存在的 key 进行大量的高并发查询,这导致缓存无法命中,每次请求都要穿透到后端数据库系统进行查询,使数据库压力过大,甚至使数据库服务被压死。
解决方案:
1、我们通常将空值缓存起来,再次接收到同样的查询请求时,若命中缓存并且值为空,就会直接返回,不会穿透的数据库,避免缓存穿透。
2、当然,有时恶意袭击者可以猜到我们使用了这种方案,每次都会使用不同的参数来查询,这就需要我们对输入的参数进行过滤,例如,如果我们使用 ID 进行查询,则可以对 ID 的格式进行分析,如果不符合产生 ID 的规则,就直接拒绝,或者在 ID 上放入时间信息,根据时间信息判断 ID 是否合法 ,或者是否是我们曾经生成的 ID,这样可以拦截一定的无效请求。
二、缓存并发
概念:
缓存并发的问题通常发生在高并发的场景下,当一个缓存 key 过期时,因为访问这个缓存 key 的请求量较大,多个请求同时发现缓存过期,因此多个请求会同时访问数据库来查询最新的数据,并且回写缓存,这样会造成应用和数据库的负载增加,性能降低,由于并发较高,甚至会导致数据库被压死。
解决方案:
1、分布式锁
使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
2、本地锁
与分布式锁类似,我们通过本地锁的方式来限制只有一个线程去数据库查询数据,而其他线程只需等待,等前面的线程查询到数据后再访问缓存。但是,这种方法只能限制一个服务节点只有一个线程去数据库中查询,如果一个服务有多个节点,则还会有多个数据库查询操作,也就是说在节点数量较多的情况下并没有完全解决缓存并发的问题。
3、软过期
软过期是指对缓存中的数据设置失效时间,就是不使用缓存服务器提供的过期时间,而是业务层在数据中存储过期时间信息,由业务程序判断是否过期并更新,在发现了数据即将过期时,将缓存的时效延长,程序可以派遣一个线程去数据库中获取最新的数据,其他线程这时看到延长了的过期时间,就会继续使用旧数据,等派遣的线程获取最新数据后再更新缓存。也可以通过异步更新服务来更新设置软过期的缓存,这样应用层就不用关心缓存并发的问题了。
三、缓存雪崩
概念:
缓存雪崩指缓存服务器重启或者大量缓存集中在某一个时间段内失效,给后端数据库造成瞬间的负载升高的压力,甚至压垮数据库的情况。
解决方案:
通常的解决办法是对不同的数据使用不同的失效时间,甚至对相同的数据、不同的请求使用不同的失效时间。例如,我们要缓存 user 数据,会对每个用户的数据设置不同的缓存过期时间,可以定义一个基础时间,假设 10 秒,然后加上一个两秒以内的随机数,过期时间为 10 ~ 12 秒,就会避免缓存雪崩。