思维草图
缓存穿透
查询不存在的数据,穿透redis缓存,请求直接攻击后端db。
问题
当系统中引入redis缓存后,一个请求进来后,会先从redis缓存中查询,缓存有就直接返回(相当于一道隔离闸,保护db),缓存中没有就去db中查询,db中如果有就会将其丢到缓存中。
但是有些key对应数据在db中并不存在,每次针对此次key的请求从缓存中取不到,请求都会压到db,从而可能压垮db。 比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用大量此类攻击可能压垮数据库。
解决方案
1.对空值缓存
如果一个查询返回的数据为空**(不管数据库是否存在),我们仍然把这个结果(null)进行缓存**,给其设置一个很短的过期时间,最长不超过五分钟。
2.设置可访问的名单(白名单)
使用redis中的bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次范文和 bitmap里面的id进行比较,如果访问的id不在bitmaps里面,则进行拦截,不允许访问。
3.采用布隆过滤器
布隆过滤器(Bloom Filter)是1970年有布隆提出的,它实际上是一个很长的二进制向量(位图)和一 系列随机映射函数(哈希函数)。 布隆过滤器可以用于检测一个元素是否在一个集合中,它的优点是空间效率和查询的世界都远远超过一 般的算法,缺点是有一定的误识别率和删除困难。 将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
4.进行实时监控
当发现redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制对其提供服务(比如:IP黑名单)。
缓存击穿
当一个热点key突然失效,或者一个数据突然变成热点数据(key还没放进缓存),请求先查询redis,发现没有,所有的请求瞬间打到后端的db上,把db打穿。
问题
redis中某个热点key(访问量很高的key)过期,此时大量请求同时过来,发现缓存中没有命中,这些请求都打到db上了,导致db压力瞬时大增,可能会打垮db,这种情况成为缓存击穿。
key可能会在某些时间点被超高并发地访问,是一种非常"热点"的数据,这个时候,要考虑一个问题,缓存被"击穿"的问题
这种情况下redis正常运行,压力全都瞬间给到了db。
解决方案
1.预先设置热门数据,适时调整过期时间
在redis高峰之前,把一些热门数据提前存入到redis里面,对缓存中的这些热门数据进行监控,实时调 整过期时间。
2.使用锁
缓存中拿不到数据的时候,此时不是立即去db中查询,而是去获取分布式锁(比如redis中的setnx), 拿到锁再去db中load数据;没有拿到锁的线程休眠一段时间再重试整个获取数据的方法。
缓存雪崩
同一时间redis种大量的key同时失效,导致请求全部请求后端db,底层服务崩溃。
问题
key对应的数据存在,但是极短时间内有大量的key集中过期,此时若有大量的并发请求过来,发现缓存没有数据,大量的请求就会落到db上去加载数据,会将db击垮,导致服务奔溃。
缓存雪崩与缓存击穿的区别在于:前者是大量的key集中过期,而后者是某个热点key过期或者某个数据突然变成热点数据。
解决方案
1.构建多级缓存
nginx缓存+redis缓存+其他缓存(ehcache等)
2.使用锁或队列
用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适用高并发情况。
3.监控缓存过期,提前更新
监控缓存,发下缓存快过期了,提前对缓存进行更新。
4.将缓存失效时间分散开(常用)
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样缓存的过期时间重复率就会降低,就很难引发集体失效的事件。
(就是不要让所有的key同一时间失效)
分布式锁
问题
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
解决方案
-
基于数据库实现分布式锁
-
基于缓存(redis等)
-
基于zookeeper
每一种分布式锁解决方案都有各自的优缺点,根据实际的业务场景进行选择
-
性能:redis最高
-
可靠性:zookeeper最高
使用redis实现分布式锁
bash
set key value NX PX 有效期(毫秒)
# 执行命令,返回ok表示执行成功,则获取锁成功,多个客户端并发执行此命令的时候,redis可确保只有一个可以执行成功。
总结
概括
- 缓存穿透是指缓存中无法找到所需数据,导致每次请求都需要访问数据库,从而增加了数据库的负载。这种情况通常发生在恶意攻击或者查询不存在的数据时。
- 缓存击穿是指一个缓存中的热点数据非常频繁地被大量并发请求访问,当该热点数据失效的瞬间,持续的大并发请求无法通过缓存获取到数据,而直接访问数据库。
- 缓存雪崩是指在同一时间段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库给数据库带来巨大压力,可能导致数据库崩溃。
差异点
- 缓存穿透 是查询不存在的数据,而缓存击穿 和缓存雪崩是查询存在的数据。
- 缓存击穿 是一个热点key失效,而缓存雪崩是大量key同时失效。
记忆方法
- 穿透:想象一个盗贼穿透了缓存直接攻击数据库,即查询不存在的数据。
- 击穿:想象一个热门景点(热点key)被大量游客(并发请求)同时访问,但景点突然关闭(数据失效),游客只能转而访问其他景点(数据库)。
- 雪崩:想象一场大雪压垮了多个景点(大量key同时失效),导致大量游客无处可去,只能涌向剩余的景点(数据库),给景点带来巨大压力。