原创 爱他就关注他---> Java分享客栈 2023-10-05 14:13 发表于湖北
收录于合集
#java82个
#springboot35个
#redis9个
点击关注公众号,Java干货及时送达👇
Java分享客栈
分享IT互联网主流技术,包括Java、SpringBoot、SpringCloud-alibaba、Redis缓存、MQ队列、网络编程、websocket通信、netty、docker、k8s等技术及多年工作经验分享和感悟。
84篇原创内容
公众号
前言
缓存穿透是一个经典的使用缓存有可能面临的问题,也是面试中Redis相关的问题中非常喜欢问的一个问题。
缓存穿透的解决方案有很多,其中公认最有效且合理的还是布隆过滤器。
而布隆过滤器的实现方案也有很多,这里我直接告诉大家,目前为止,最为推荐的是Redisson实现的布隆过滤器。
Redisson作为Redis分布式锁的优秀封装框架,其实解决了Redis很多企业级的问题,对布隆过滤器也有较简便的实现。
今天我就将Redisson解决缓存穿透的方案通过一个案例分享给大家,希望有所帮助。
缓存穿透
首先,要明白什么是缓存穿透。
缓存穿透是指在正常的业务逻辑下,会先查询缓存再查询数据库,当数据库中存在就返回并且放入缓存,不存在就返回空对象。
那么在此时,如果恶意不断地传递一个不存在的key给redis,比如-1或UUID,那么永远不会查到,那么每次请求都会越过redis去访问数据库,给数据库造成压力,在特定时机如流量洪峰期可能直接压垮数据库。
解决方案
我这里说两种常见的方案:
1)、在缓存中没有查到,去数据库查询的时候,如果查询为空,也放入到redis中,但是设置很短的过期时间,比如60s,这样就可以防止缓存穿透对数据库的恶意攻击;
2)、布隆过滤器。
第一种方式很简单,但在高并发场景下不一定百分百能解决,比如60s的过期时间内,极端场景下还是有可能出现缓存穿透。
第二种就是目前为止的最佳方案,也是更推荐的。
案例
1、引入依赖
xml
<dependencies>
<!-- Spring Boot Starter Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.1</version>
</dependency>
</dependencies>
2、创建工具类
创建一个 RedissonUtil 工具类来管理 Redisson 客户端的初始化和布隆过滤器的操作
java
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedissonUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Bean
public RedissonClient redissonClient() {
// 初始化 Redisson 客户端连接
return Redisson.create();
}
public RBloomFilter<String> createBloomFilter(String name, long expectedInsertions, double falseProbability) {
RBloomFilter<String> bloomFilter = redissonClient().getBloomFilter(name);
bloomFilter.tryInit(expectedInsertions, falseProbability);
return bloomFilter;
}
}
3、创建工具类
在业务逻辑代码中,使用 RedissonUtil 和 RBloomFilter 来进行缓存穿透的判断和缓存处理。
java
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@Autowired
private RedissonUtil redissonUtil;
@Autowired
private ProductRepository productRepository;
public Product getProductById(Long id) {
String key = "product:" + id.toString();
// 创建布隆过滤器,预计元素数量为10000,误判率为0.01
RBloomFilter<String> bloomFilter = redissonUtil.createBloomFilter("product_bloom_filter", 10000, 0.01);
// 检查布隆过滤器中是否可能存在该键
if (!bloomFilter.contains(key)) {
// 不存在的情况下,直接返回空对象或抛出异常等处理方式
return null;
}
// 从缓存中获取数据
Product cachedProduct = getCachedProduct(id);
if (cachedProduct != null) {
return cachedProduct;
}
// 从数据库查询数据
Product dbProduct = productRepository.findById(id).orElse(null);
if (dbProduct != null) {
// 将数据库查询结果放入缓存
cacheProduct(dbProduct);
bloomFilter.add(key);
}
return dbProduct;
}
private Product getCachedProduct(Long id) {
// 根据缓存逻辑从 Redis 中获取数据
// ...
}
private void cacheProduct(Product product) {
// 将数据写入 Redis 缓存
// ...
}
}
简单解释下,
RedissonUtil
提供了对 Redisson 客户端和布隆过滤器的初始化和操作方法。
getProductById()
方法先使用RBloomFilter.contains()
进行判断,如果不存在该键,直接返回空对象或抛出异常。如果可能存在该键,则继续查询缓存和数据库,并将查询结果放入缓存和布隆过滤器中。
总结
通过结合 Redisson 和布隆过滤器的使用,您可以有效地解决缓存穿透问题,并减轻对数据库的访问压力。
当然,实际工作中使用,要根据实际需求调整布隆过滤器的参数设置,达到一个最佳性能。
另外,告诉大家一点,Hutool工具类也提供了布隆过滤器的实现,有兴趣的可以去官网文档看看。
好了,今天的小知识你学会了吗?