缓存雪崩&缓存击穿&缓存穿透
缓存雪崩&缓存击穿&缓存穿透
缓存雪崩
1、什么是缓存雪崩
如果缓存再某一时刻出现大规模的key失效
,就会导致大量请求打在了数据库上面,导致数据压力巨大,如果在高并发的情况下,可能瞬间就会导致数据宕机。
2、解决方案
事前
- 均匀过期:设置不同的过期时间,让缓存失效的时间尽量均匀,避免相同的过期时间导致缓存雪崩,造成大量数据库的访问。
- 分级缓存:第一级缓存失效的基础上,访问二级缓存,每一级缓存的失效时间都不同。
- 热点数据缓存永远不过期。
永不过期实际包含两层意思:
物理不过期,针对热点key不设置过期时间
逻辑过期,把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建
- 保证Redis缓存的高可用,防止Redis宕机导致缓存雪崩的问题。可以使用 主从+ 哨兵,Redis集群来避免 Redis 全盘崩溃的情况。
事中
- 互斥锁:在缓存失效后,通过互斥锁或者队列来控制读数据写缓存的线程数量,比如某个key只允许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时系统的吞吐量会下降。
- 使用熔断机制,限流降级。当流量达到一定的阈值,直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上将数据库击垮,至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。
事后
- 开启Redis持久化机制,尽快恢复缓存数据,一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。
缓存穿透
1、什么是缓存穿透
缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍。如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而宕机崩溃。
2、解决方案
方案一:空对象缓存或者缺省值
一旦发生缓存穿透,我们就可以针对查询的数据,在Redis中缓存一个空值或是和业务层协商确定的缺省值(例如,库存的缺省值可以设为0)。紧接着,应用发送的后续请求再进行查询时,就可以直接从Redis中读取空值或缺省值,返回给业务应用了,避免了把大量请求发送给数据库处理,保持了数据库的正常运行。
方案二:Redis布隆过滤器解决缓存穿透
布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小。
白名单架构说明
3、Redis布隆过滤器使用案例
安装RedisBloom
Redis 在 4.0 之后有了插件功能(Module),可以使用外部的扩展功能,可以使用 RedisBloom 作为 Redis 布隆过滤器插件。
推荐用docker安装
启动
docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
进入redis-cli
docker exec -it [CONTAINER ID] /bin/bash
代码
/**
* @Author: LR
* @Date: 2022/6/17 14:26
**/
public class RedissonBloomFilter {
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
//config.useSingleServer().setPassword("root");
//构造Redisson
RedissonClient redisson = Redisson.create(config);
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("userList");
//初始化布隆过滤器:预计元素为100000000L,误差率为3%
bloomFilter.tryInit(100000000L,0.03);
//将userid admin 插入布隆过滤器
bloomFilter.add("admin");
//判断下面userId是否在布隆过滤器中
System.out.println(bloomFilter.contains("admin22"));//false
System.out.println(bloomFilter.contains("admin"));//true
}
}
缓存击穿
1、什么是缓存击穿
缓存击穿跟缓存雪崩有点类似,缓存雪崩是大规模的key失效,而缓存击穿是某个热点的key失效
,大并发集中对其进行请求,就会造成大量请求读缓存没读到数据,从而导致高并发访问数据库,引起数据库压力剧增。这种现象就叫做缓存击穿。
2、解决方案
物理不过期,针对热点key不设置过期时间
互斥独占锁防止击穿
public User test(Integer userId) {
User user = null;
//缓存key
String key = CACHE_KEY_USER + userId;
//1 查询redis
user = (User) redisTemplate.opsForValue().get(key);
//redis无,进一步查询mysql
if (user == null) {
//对于高QPS的优化,进来就先枷锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql
synchronized (UserService.class) {
user = (User) redisTemplate.opsForValue().get(key);
//2 二次查询redis还是null,可以去查mysql了
if (user == null) {
//从mysql查出来user
user = this.userMapper.selectByPrimaryKey(userId);
if (user == null) {
return null;
} else {
//mysql有数据,redis没有数据,则需要再写入redis,保证数据的一致性
redisTemplate.opsForValue().set(key, user);
}
}
}
}
return user;
}