前言
在现代高并发的互联网应用中,缓存技术已成为提升系统响应速度与减轻后端数据库压力的关键手段。Redis,以其卓越的性能和丰富的数据结构,成为众多开发者构建缓存层的首选。然而,随着业务复杂度的增加,Redis缓存层也可能遭遇“缓存穿透”、“缓存击穿”以及“缓存雪崩”等现象,这些情况不仅影响系统的稳定性和响应时间,还可能直接导致服务的不可用。本文旨在深入剖析这三种缓存异常现象,探讨其背后的原理,并提出有效的预防策略。
缓存穿透
一般的缓存系统都是通过key-value的形式来缓存数据,如果key对应的value不存在,则应该去后端系统(DB)查询,一些恶意程序会故意查询不存在的key,请求量很大,从而可能压垮数据源,这就叫-缓存穿透。比如用一个不存在的用户id去查询用户信息,此时缓存中没有该用户,就会去数据库查询。如果利用此漏洞进行攻击可能压垮数据库。
解决方案
一个一定不存在及查询不到的数据,由于缓存是不命中的时候被动进行写操作的,那么如果从DB查询不到数据,那么每次有请求进来都会从DB进行查询。所以有两种解决方案:
方案1-将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据就会被这个bitmap拦截掉,从而避免了对底层DB的压力。
方案2-对于key从DB查询的结果,如果为空也进行存储,只是过期时间设置短一点(一般不超过5分钟),这样再有同样的大数量访问进来也不会对后端造成压力。
对于方案1,常用的就是布隆过滤器(BloomFilter),而布隆过滤器实质上就是一个二进制数组和一系列的hash函数。
补充说明: 在判断布隆过滤器中是否存在时,它会进行多次hash,寻找位阵列中的值,如果多次hash的值有一个为0,那么说明不存在,如果都是1,那么也只能说明可能存在(因为可能存在hash碰撞)。解决办法:1、多次hash,2、增加二进制数组长度
那么如何创建并使用布隆过滤器呢?
1、使用redisson中的boolmFilter创建
public void patchingConsum(ConsumPatchingVO vo) throws ParseException {
Config config = new Config();
SingleServerConfig singleServerConfig = config.useSingleServer();
singleServerConfig.setAddress("redis://127.0.0.1:6379");
singleServerConfig.setPassword("123456");
RedissonClient redissonClient = Redisson.create(config);
RBloomFilter<String> bloom = redissonClient.getBloomFilter("name");
// 初始化布隆过滤器; 大小:100000,误判率:0.01
bloom.tryInit(100000L, 0.01);
// 新增10万条数据
for(int i=0;i<100000;i++) {
// 模拟将redis的key放入boolmfilter
bloom.add("name" + i);
}
// 判断不存在于布隆过滤器中的元素
List<String> notExistList = new ArrayList<>();
for(int i=0;i<100000;i++) {
String str = "name" + i;
// 判断key在redis中是否存在
boolean notExist = bloom.contains(str);
if (notExist) {
notExistList.add(str);
}
}
2、使用google的guava包
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
@Configuration
public class BloomFilterConfig {
@Bean
public BloomFilter<String> bloomFilter() {
return BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 1000000, 0.01);
}
}
对于方案2,很好理解,如果查询结果为null,也存入redis中,只是将expireTime设置的小一些,具体根据业务决定,此处就不做详细说明啦。
缓存击穿
对于非常热点的数据,可能会在某些时间点被超高并发访问,那么如果在这个时间缓存失效,那么此时这并发的些高访问就会去访问数据库,这样就可能会压垮数据库。
解决方案
方案1-使用redis互斥锁
方案2-对于某一热点,将其设置为永不过期。比如秒杀的爆款,设置为永不过期。
redis互斥锁一般来讲也有两种实现方法:
1、最简单的可以使用redis中的set和delete,实现加锁和解锁,当然这种方式会有问题,例如:操作不当可能会造成锁误删。此处就不再展开,有需要以后单独发一篇介绍。
2、第二种就是redisson看门狗,可以实现自动续期。
RLock lock = redissonClient.getLock("testLock");
try {
lock.lock();
boolean tryLock = lock.tryLock(10, -1, TimeUnit.SECONDS);
if (tryLock) {
log.info("redisson 加锁...");
// 业务操作...
}
} catch (Exception exception) {
} finally {
// 锁释放,防止异常时锁一直持有。
lock.unlock();
}
缓存雪崩
缓存雪崩和缓存击穿的区别在于缓存击穿针对的是某一个热点key,而雪崩指的是一批key在某一时间点集体失效过期,那么此时就会去数据库加载,从而会导致大量的线程对数据库进行读写从而造成数据库的瞬间压力。
解决方案
在原有的过期时间上对过期时间进行随机,这样可以减少缓存的过期时间重复率。
标签:缓存,Java,示例,过期,数据库,redis,查询,key From: https://blog.csdn.net/weixin_43834477/article/details/140378348