缓存雪崩、缓存击穿、缓存穿透详解以及区别和解决方案
1. 缓存雪崩
1.1 什么是缓存雪崩?
缓存雪崩指的是由于缓存服务器在同一时间大面积失效或宕机,导致大量请求直接访问数据库,瞬间引发数据库压力激增,甚至导致数据库崩溃。
1.2 造成缓存雪崩的原因
- 同一时间大量缓存失效:如果缓存设置了相同的过期时间,到了某个时间点,大量缓存同时失效,所有请求直接访问数据库,造成数据库压力骤增。
- 缓存服务器宕机:缓存服务器因故障无法提供服务,导致请求全部直接访问数据库。
1.3 解决方案
- 缓存过期时间设置为随机值:
-
- 不要让所有缓存同时过期,可以通过设置缓存的过期时间为随机值来避免。
- 例如,缓存的过期时间
TTL
设为一个基准值加上一个随机的时间偏移量。
redisTemplate
.boundValueOps(DOCTOR_CACHE+doctorId)
.expire(
DOCTOR_CACHE_EXPIRE +
new Random().nextInt(7200),
TimeUnit.SECONDS
);
- 加固缓存系统:
-
- 使用分布式缓存架构,避免单点故障。常用的分布式缓存有Redis Cluster、Memcached等。
- 为缓存服务配置备份节点和集群架构,防止因缓存宕机引发系统崩溃。
- 限流降级(nginx IP限流):
-
- 在缓存失效时,对请求进行限流,防止过多请求涌入数据库。可以通过限流器(如漏桶算法、令牌桶算法)等方式来控制请求量。
- 同时,在缓存不可用时可以返回一些默认值或者降级的数据。
2. 缓存击穿
2.1 什么是缓存击穿?
缓存击穿是指某个热点数据在缓存中失效的瞬间,大量的并发请求同时访问该数据,由于该数据在缓存中失效,大量请求同时访问数据库,造成数据库压力骤增。这种情况通常发生在热点数据或访问频繁的数据上。
2.2 解决方案
- 互斥锁(Mutex)机制:
-
- 当缓存失效时,通过加锁的方式保证只有一个线程去查询数据库并更新缓存,其他线程等待缓存更新完成后再获取数据。
- 预加载缓存:
-
- 对一些热点数据提前加载,并定期刷新缓存,防止缓存失效。
- 使用不过期的缓存:
-
- 对于极为重要的热点数据,可以设置其缓存永不过期,同时后台启动线程定期刷新该缓存。
- 物理上不设置失效时间,逻辑控制(代码层面控制)
3. 缓存穿透
3.1 什么是缓存穿透?
缓存穿透是指客户端频繁访问一些根本不存在的缓存数据,由于缓存中没有这些数据的记录,每次请求都直接访问数据库,导致数据库压力增大,但是数据库中也不存在。
这通常是由于用户输入非法或恶意构造的请求引发的。
3.2 解决方案
- 缓存空值(缓存空标记):
-
- 当查询一个不存在的key时,先访问缓存,缓存中没有访问数据库,数据库中也没有用redis做一个空标记 ,将空结果也写入缓存,并设置一个较短的过期时间 。下次再遇到相同的请求时判断是否存在空标记 key,存在这个空标记则直接返回缓存中的空值,避免再次查询数据库。
- 存在问题:当这种空标记过多,会消耗资源,所以这种情况采用布隆过滤器
- 布隆过滤器(Bloom Filter):
-
- 在查询缓存和数据库之前,利用布隆过滤器来存储这个 key ,判断key是否存在。
- 存在则直接放行;
- 如果该key不存在则直接被拦截返回,不必查询缓存或数据库,节约存储空间。
- 限流降级(nginx IP限流):
-
- 在缓存失效时,对请求 IP 进行限流,防止过多请求涌入数据库。可以通过限流器(如漏桶算法、令牌桶算法)等方式来控制请求量。
- 同时,在缓存不可用时可以返回一些默认值或者降级的数据。
- 参数校验(前端参数检验):
-
- 在系统前端或者应用层对请求的参数进行有效性验证,过滤掉明显无效或恶意的请求。
4. nginx 限流算法
1. 令牌桶算法
令牌以固定速率产生,并缓存到令牌桶中;
发起的请求队列要消耗同等比例的令牌才能被处理;(只有拿出对应比例的令牌才能执行对应数量的请求)
令牌桶放满时,多余的令牌被丢弃;
令牌不够时,请求被缓存。
2. 漏桶算法
请求从上方倒入漏桶,从漏桶下方流出处理;
来不及处理的请求会在漏桶中进行缓冲(缓存),这些请求以固定速率流出;
漏桶满后,也就是请求溢出则直接丢弃。
算法的核心:缓存请求、匀速处理、多余的请求直接丢弃。
漏桶算法,令牌桶算法 不同之处
令牌桶算法它不但有一只“桶”,还有个队列,这个桶是用来存放令牌的,队列才是用来存放请求的;
漏桶算法则是将请求直接缓存放在“桶”中。
5. 总结分析方案以及区别
- 缓存雪崩:通过设置随机过期时间、使用分布式缓存和限流降级来应对。
- 缓存击穿:通过互斥锁、预加载缓存(缓存预热)等手段解决热点数据失效引发的数据库压力。
- 缓存穿透:通过缓存空值、使用布隆过滤器和参数校验来防止无效请求访问数据库。
名称 | 区别 | 解决方案 |
缓存雪崩 | 由于缓存服务器在同一时间大面积失效或宕机,导致大量请求直接访问数据库,瞬间引发数据库压力激增,甚至导致数据库崩溃 | 缓存过期时间设为随机:不要让所有缓存同时过期,可以通过设置缓存的过期时间为随机值来避免。例如,缓存的过期时间 加固缓存:为分布式缓存架构的缓存服务配置备份节点和集群架构,防止因缓存宕机引发系统崩溃。 限流降级:在缓存失效时,对请求进行限流,防止过多请求涌入数据库。可以通过限流算法漏桶、令牌桶等方式来控制请求量。同时,在缓存不可用时可以返回一些默认值或者降级的数据。 |
缓存击穿 | 数据在缓存失效的瞬间,大量的并发请求同时访问该数据,造成数据库压力骤增 | 互斥锁:加锁的方式保证只有一个线程去查询数据库并更新缓存,其他线程等待缓存更新完成后再获取数据。 预加载缓存:对热点数据提前加载,并定期刷新缓存,防止缓存失效。 设置不过期缓存:对于极为重要的热点数据,可以设置其缓存永不过期,同时后台启动线程定期刷新该缓存。 逻辑控制失效时间:物理上不设置失效时间,逻辑控制(代码层面控制),如果一直访问就不断续期 |
缓存穿透 | 频繁访问一些根本不存在的缓存数据,由于缓存中没有这些数据的记录,每次请求都直接访问数据库,导致数据库压力增大,但是数据库中也不存在。 | 缓存空标记:当查询一个不存在的key时,先访问缓存,缓存中没有访问数据库,数据库中也没有就用redis做一个空标记 ,将空结果也写入缓存,并设置一个较短的过期时间 。再遇到相同的请求时判断是否存在空标记 key,存在这个空标记则说明数据不存在直接返回缓存中的空值,避免再次查询数据库。但是一旦这种空标记过多会消耗大量资源。 布隆过滤器(较优方案):在查询缓存和数据库之前,利用布隆过滤器来存储这个 key ,判断key是否存在。存在则直接放行; 如果该key不存在则直接被拦截返回,不必查询缓存或数据库,节约存储空间。 nginx ip 限流:在缓存失效时,通过 nginx 对请求 ip 进行限流,防止过多请求涌入数据库。可以通过限流算法漏桶、令牌桶等方式来控制请求量。同时,在缓存不可用时可以返回一些默认值或者降级的数据。 前端参数检验:在系统前端或者应用层对请求的参数进行有效性验证,过滤掉明显无效或恶意的请求。 |