3分钟看完,SpringBoot + Redis + 布隆过滤器 一次搞定缓存穿透问题

原创 爱他就关注他---> 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工具类也提供了布隆过滤器的实现,有兴趣的可以去官网文档看看。

好了,今天的小知识你学会了吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Sure!以下是一个示例代码,展示了如何在Java Spring Boot中使用布隆过滤器Redis来解决缓存穿透问题: 首先,你需要在pom.xml文件中添加相应的依赖: ```xml <dependencies> <!-- Spring Boot Starter Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Guava Bloom Filter --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1-jre</version> </dependency> </dependencies> ``` 接下来,创建一个布隆过滤器的工具类 BloomFilterUtil.java: ```java import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public class BloomFilterUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; private BloomFilter<String> bloomFilter; // 设置布隆过滤器的预计插入数据量和误判率 private static final int EXPECTED_INSERTIONS = 1000000; private static final double FPP = 0.001; @PostConstruct public void init() { // 创建布隆过滤器,并将其保存到Redis中 bloomFilter = BloomFilter.create(Funnels.stringFunnel(), EXPECTED_INSERTIONS, FPP); redisTemplate.opsForValue().set("bloomFilter", bloomFilter); } public boolean mightContain(String key) { // 从Redis中获取布隆过滤器 bloomFilter = (BloomFilter<String>) redisTemplate.opsForValue().get("bloomFilter"); // 使用布隆过滤器判断key是否可能存在 return bloomFilter.mightContain(key); } public void put(String key) { // 从Redis中获取布隆过滤器 bloomFilter = (BloomFilter<String>) redisTemplate.opsForValue().get("bloomFilter"); // 将key添加到布隆过滤器中 bloomFilter.put(key); // 将更新后的布隆过滤器保存到RedisredisTemplate.opsForValue().set("bloomFilter", bloomFilter); } } ``` 然后,在你需要使用布隆过滤器解决缓存穿透的地方,注入 BloomFilterUtil,并使用它来判断数据是否存在于缓存中: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class CacheController { @Autowired private BloomFilterUtil bloomFilterUtil; @GetMapping("/data/{key}") public String getData(@PathVariable String key) { // 先使用布隆过滤器判断key是否可能存在于缓存中 if (bloomFilterUtil.mightContain(key)) { // 如果可能存在,再从缓存中获取数据 String data = redisTemplate.opsForValue().get(key); if (data != null) { return data; } } // 如果数据不在缓存中,进行其他操作(例如从数据库中查询数据) // ... return null; } } ``` 这样,当有大量的请求同时访问某个缓存时,在经过布隆过滤器的判断后,可以避免无效的缓存查询请求,减轻了数据库的负载压力。 请注意,以上代码只是示例,实际使用时需要根据具体的业务需求进行适当的修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值