一、缓存穿透
查询的数据既不在redis缓存中,也不在数据库中。所以每次查询都会穿过redis直达数据库,还无法被redis缓存到。这种一般都是人为攻击较多。
解决方法一:假数据
既然是人为攻击,你还跟他客气什么?直接塞假数据呀。一来不会穿过redis造成数据库的压力;二来小小的“报复”一下,皮一下。
String key = "一个不存在的key";
String value = (String) redisTemplate.opsForValue().get(key);
if (ObjectUtils.isEmpty(value)
&& ObjectUtils.isEmpty(value = selectFromDataBase(key)))
redisTemplate.opsForValue().set(key, "我是你爹", 10, TimeUnit.SECONDS);
} else {
redisTemplate.opsForValue().set(key, value);
}
解决方法二:布隆过滤器
在客户端和缓存中间加上布隆过滤器,可以将百分之百不存在的key过滤掉。
原理:布隆过滤器是一个大型的二进制数组,每次添加key时,都将key的hash值取模该数组的长度,将对应索引的值置为1。布隆过滤器维护着多个hash计算的函数,将key跟多个hash函数取模后得到多个下标索引,将数组索引上的值全部置为1。
当需要查询key时,布隆过滤器会重新将key跟维护着的多个hash函数进行运算计算出对应的数组索引。
- 只要一个索引为0,那么这个key必然不存在。因为只要这个key存在,得到的索引上都应该是1
- 所有的索引都为1,却不能确定key一定存在。因为有可能是其他key重合了这个索引
- 布隆过滤器数组长度越长,那么索引分布的越稀疏,这个key存在的可能性越大
这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景, 代码维护较为复杂, 但是缓存空间占用很少。
代码实现
导入 redission 依赖包
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
代码
// 创建 redission 的config对象
Config config = new Config();
// 设置 redis 服务器信息
config.useSingleServer().setAddress("redis://172.168.1.1:6379", "redis://172.168.1.2:6379");
// 设置密码
// config.useSingleServer().setPassword("password");
// 获得 redisson 对象
RedissonClient redisson = Redisson.create(config);
// 获取布隆过滤器
// bloom 布隆过滤器名称
RBloomFilter<Object> bloom = redisson.getBloomFilter("bloom");
// 初始化布隆过滤器,预计元素 100000000L 个,误差率 3%
// 根据这两个参数计算出底层的bit数组大小
myBloom.tryInit(100000000L,0.03);
// 模拟从数据库中向过滤器添加数据
for (int i = 1; i <= 10; i++) {
if(!myBloom.contains("k" + i)) {
String dbVal = selectFromDB(i);
if(dbVal != null) {
//向布隆过滤器插入key
myBloom.add("k" + i);
}
}
}
// 查询缓存
for (int i = 1; i < 10; i++) {
//-------> 判断key在布隆过滤器中是否存在
boolean flag = myBloom.contains("k" + i);
if(flag) {
//key有可能存在
String value = (String) redisTemplate.opsForValue().get("k" + i);
if(ObjectUtils.isEmpty(value)) {
//从数据库查询
System.out.println("============>从数据库查询" + i);
value = selectFromDB(i);
redisTemplate.opsForValue().set("k" + i, value);
} else {
System.out.println("---------->从缓存中查询" + i);
}
} else {
//key一定不存在
System.out.println("--------->非法请求");
}
}
二、缓存击穿
缓存中某个热点数据的key突然过期,造成大量的请求无法从缓存中获取数据,直接打到数据库。
解决方法
- 热点数据设置更长的过期时间
- 限流。请求达到一定阈值,给一个访问频繁等提示
- 降级。不访问数据库,直接给出默认
三、缓存雪崩
缓存中大量的 key 在同一时间过期,造成大量的请求无法从缓存中获取数据,而直达数据库。感觉就像出现了雪崩。
京东秒杀,秒杀10000件商品【这10000件商品设置了相同的生存的时间】,在同一时间秒杀【在10:10分时,这10000件商品同时过期】
解决
- 热点数据永不过期
- 不同的key随机生存时间