缓存穿透
缓存穿透有两种情况:
一种是查询的数据在数据库中存在,另一种是数据在数据库中不存在。
- 第一种情况的缓存穿透出现在高并发场景下,要查询的数据还未被缓存到redis,我们期望的是,查询一次数据库,然后将数据写入缓存,但是高并发场景下,一下子进来成百上千个请求,我们将数据库数据写入缓存需要时间,在写入已经开始但还未完成时,就会有很多查询请求直接打到数据库上造成高并发场景下数据库的崩坏。
- 第二种情况的缓存穿透指的是,数据在数据库中不存在。 代码逻辑是:得到查询请求,先去redis中找,找到了就直接返回,没找到就查数据库,如果数据查出来不为空,
那么就将数据库数据缓存到redis,查出来数据为空,就返回空。当数据不存在时,redis里面肯定没有,数据库里也没有,每次请求都要去数据库里查,并且还查不到,查不到就不会缓存到redis,所以每一次都查数据库,高并发下数据库就容易崩坏。
缓存穿透的应对措施:
- 对请求增加校验机制
对一些访问量比较大的请求,给他需要的参数字段设置固定的格式,比如查询课程信息,需要的参数为课程Id,给这个字段设定格式为课程Id+课程名的MD5,不满足这个格式的参数直接就进不来,都进不到查询缓存那一步,更别说查询数据库了。 - 使用布隆过滤器
布隆过滤器加在缓存与数据库之间,他的作用是判断一个数据在某个集合中是否存在,存在返回1,不存在返回0,他如果判断为不存在,那就是一定不存在,但是他如果判断为存在,这就不一定存在,可能还是不存在。我们只需要利用它判断不存在的正确率为100就行,让他挡在数据库前面,判断不存在就直接返回,判断存在,实际上不存在,这种情况是小概率事件,因此请求就不会大量打到数据库上。
上述两种措施通常搭配缓存预热来使用,在高并发场景下,系统上线前,把数据库中的数据尽可能都缓存一遍,避免临时写入缓存。
- 修改代码逻辑,缓存空值
通过参数校验以后,缓存中不存在,去查数据库,数据库也不存在,那么我们也要缓存这个不存在的值,使得最起码当前这个请求下次再来的时候,不会打到数据库上。
缓存击穿
描述:缓存击穿描述的是热点数据在某一个时刻,它在redis中设置的缓存过期了,但是外界还是会有大量的请求访问这个数据,缓存中没有,那这些请求就全部打到数据库上了,瞬间耗光数据库资源,导致数据库不可用。
缓存击穿解决措施:
-
加锁互斥
在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。 -
热点数据不过期。
直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。
热点数据不过期适用于比较极端的场景,例如流量特别特别大的场景,使用时需要考虑业务能接受数据不一致的时间,还有就是异常情况的处理,不要到时候缓存刷新不上,一直是脏数据,那就凉了。
缓存雪崩
描述:redis中大量的Key在同一时间过期,大量的请求直接打在数据库上,瞬间耗光数据库资源,导致数据库不可用。
缓存雪崩应对措施:
-
使用同步锁控制查询数据库的线程
加锁排队,每次只能有一个线程查询数据库写入缓存,效率很低,但可以防止数据库被冲垮。 -
为同一类型的key设置不同的过期时间
比如,给同一类服务的缓存数据的过期时间设置为300秒再加一个随机数,使得这一类缓存的过期分布均匀。 -
采用定时更新缓存的策略
写一个定时任务,一批缓存快过期或者过期的时候,更新它们的过期时间。 -
双层缓存策略
对数据缓存两次,K1为初始缓存,过期时间短;K2为拷贝缓存,过期时间长,当K1过期了可以访问K2。