基于Spring Boot集成Redisson实现布隆过滤器
在高并发和大数据量的场景下,布隆过滤器是一种非常高效的存储结构,可以用于快速判断一个元素是否存在于集合中。本文将介绍如何在Spring Boot中集成Redisson来实现布隆过滤器,并通过一个订单查询的示例来展示其应用。
1. 项目搭建
1.1 创建Spring Boot项目
首先,创建一个Spring Boot项目,并添加以下依赖:
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
1.2 配置Redisson
在application.yml
文件中配置Redisson的连接信息:
yaml
spring:
application:
name: redisson-bloom-filter-demo
redisson:
config:
single-server-config:
address: "redis://127.0.0.1:6379"
password: "your-redis-password"
database: 0
connection-pool-config:
max-idle: 10
min-idle: 1
max-active: 100
min-evictable-idle-time: 300000
time-between-eviction-runs: 30000
block-wait-time: 1000
idle-instance-soft-abandon: false
idle-instance-close-timeout: 30000
bloom:
filter:
expected-insertions: 1000000 # 预期插入数量,默认值为 1000000
false-probability: 0.03 # 误判率,默认值为 3%
1.3 Redisson配置类
java
package com.example.redission.configuration;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.spring.starter.RedissonProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfiguration {
@Autowired
private RedissonProperties redissonProperties;
@Bean
public RedissonClient redissonClient() throws Exception{
Config config = Config.fromYAML(redissonProperties.getConfig());
Redisson.create(config);
return Redisson.create(config);
}
}
2. 布隆过滤器实现
2.1 创建布隆过滤器服务
创建一个BloomFilterService
类,用于初始化和操作布隆过滤器:
java
package com.example.redission.service;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class BloomFilterService {
private static final Logger logger = LoggerFactory.getLogger(BloomFilterService.class);
@Value("${bloom.filter.expected-insertions:1000000}")
private long expectedInsertions;
@Value("${bloom.filter.false-probability:0.03}")
private double falseProbability;
private static final String BLOOM_FILTER_NAME = "bloomFilter";
private final RedissonClient redissonClient;
private RBloomFilter<String> bloomFilter;
public BloomFilterService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
public synchronized void initBloomFilter() {
if (bloomFilter == null) {
bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
// 检查布隆过滤器是否已经初始化
if (!bloomFilter.isExists()) {
bloomFilter.tryInit(expectedInsertions, falseProbability);
logger.info("Bloom filter initialized with expected insertions: {} and false probability: {}",
expectedInsertions, falseProbability);
} else {
logger.info("Bloom filter already exists, using existing instance");
}
}
}
public void add(String key) {
if (key == null) {
logger.warn("Attempt to add null key to Bloom filter");
return;
}
bloomFilter.add(key);
logger.debug("Added key: {} to Bloom filter", key);
}
public boolean contains(String key) {
if (key == null) {
logger.warn("Attempt to check null key in Bloom filter");
return false;
}
boolean result = bloomFilter.contains(key);
logger.debug("Checked key: {} in Bloom filter, result: {}", key, result);
return result;
}
/**
* 重新初始化布隆过滤器(仅在需要清空时调用)
*/
public synchronized void reinitialize() {
bloomFilter.delete();
bloomFilter.tryInit(expectedInsertions, falseProbability);
logger.info("Bloom filter reinitialized");
}
/**
* 获取布隆过滤器的错误概率
*/
public double getFalseProbability() {
return falseProbability;
}
/**
* 获取布隆过滤器的预期插入数量
*/
public long getExpectedInsertions() {
return expectedInsertions;
}
}
2.2 初始化布隆过滤器
创建一个BloomFilterInitializer
类,在应用启动时初始化布隆过滤器:
java
package com.example.redission.runner;
import com.example.redission.dto.OrderDetailDTO;
import com.example.redission.service.BloomFilterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
@Component
public class BloomFilterInitializer implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(BloomFilterInitializer.class);
private final BloomFilterService bloomFilterService;
@Autowired
public BloomFilterInitializer(BloomFilterService bloomFilterService) {
this.bloomFilterService = bloomFilterService;
}
@Override
public void run(String... args) throws Exception {
// 在应用启动时初始化布隆过滤器
bloomFilterService.initBloomFilter();
logger.info("Bloom filter initialized during application startup");
// 模拟订单数据并写入布隆过滤器
List<OrderDetailDTO> orderData = generateSampleOrderData();
initializeBloomFilterWithOrderData(orderData);
}
private void initializeBloomFilterWithOrderData(List<OrderDetailDTO> orderData) {
// 将订单数据写入布隆过滤器
for (OrderDetailDTO order : orderData) {
// 将订单号写入布隆过滤器
bloomFilterService.add(order.getOrderNumber());
// 将订单中的商品 SKU ID 写入布隆过滤器
for (OrderDetailDTO.OrderItemDTO item : order.getItems()) {
bloomFilterService.add(item.getSkuId());
}
}
logger.info("Bloom filter initialized with {} orders and their items", orderData.size());
}
private List<OrderDetailDTO> generateSampleOrderData() {
// 使用 Arrays.asList 替代 List.of(JDK 8 兼容)
return Arrays.asList(
new OrderDetailDTO(
"1",
"ORD-001",
new BigDecimal("100.00"),
"COMPLETED",
"PAID",
"2024-01-01 12:00:00",
Arrays.asList(
new OrderDetailDTO.OrderItemDTO(
"SKU-001",
"Product 1",
"image1.jpg",
2,
new BigDecimal("50.00"),
new BigDecimal("100.00")
)
)
),
new OrderDetailDTO(
"2",
"ORD-002",
new BigDecimal("200.00"),
"COMPLETED",
"PAID",
"2024-01-02 12:00:00",
Arrays.asList(
new OrderDetailDTO.OrderItemDTO(
"SKU-002",
"Product 2",
"image2.jpg",
1,
new BigDecimal("200.00"),
new BigDecimal("200.00")
)
)
)
);
}
}
3. 订单查询功能实现
3.1 创建订单查询服务
创建一个OrderDetailDTO
类:
java
package com.example.redission.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class OrderDetailDTO {
private String orderId; // 订单ID
private String orderNumber; // 订单号
private BigDecimal totalAmount; // 总金额
private String orderStatus; // 订单状态
private String paymentStatus; // 支付状态
private String createTime; // 创建时间
private List<OrderItemDTO> items; // 订单商品项
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class OrderItemDTO {
private String skuId; // 商品SKU ID
private String skuName; // 商品名称
private String skuImage; // 商品图片
private int quantity; // 商品数量
private BigDecimal price; // 商品单价
private BigDecimal subtotal; // 商品小计
}
}
创建一个OrderQueryService
类,用于查询订单信息:
java
package com.example.redission.service;
import com.example.redission.dto.OrderDetailDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Arrays;
@Service
public class OrderQueryService {
private static final Logger logger = LoggerFactory.getLogger(OrderQueryService.class);
private final BloomFilterService bloomFilterService;
@Autowired
public OrderQueryService(BloomFilterService bloomFilterService) {
this.bloomFilterService = bloomFilterService;
}
/**
* 查询订单信息
*
* @param orderNumber 订单号
* @return 订单信息,如果不存在则返回 null
*/
public OrderDetailDTO getOrderDetails(String orderNumber) {
// 先通过布隆过滤器判断订单号是否存在
if (!bloomFilterService.contains(orderNumber)) {
logger.info("Order {} does not exist in Bloom filter", orderNumber);
return null;
}
// 模拟订单数据
OrderDetailDTO order = generateSampleOrderData(orderNumber);
if (order != null) {
logger.info("Order {} found in simulated data", orderNumber);
return order;
} else {
logger.info("Order {} exists in Bloom filter but not found in simulated data", orderNumber);
return null;
}
}
/**
* 模拟生成订单数据
*
* @param orderNumber 订单号
* @return 模拟的订单数据
*/
private OrderDetailDTO generateSampleOrderData(String orderNumber) {
// 模拟的订单数据
if ("ORD-001".equals(orderNumber)) {
return new OrderDetailDTO(
"1",
"ORD-001",
new BigDecimal("100.00"),
"COMPLETED",
"PAID",
"2024-01-01 12:00:00",
Arrays.asList(
new OrderDetailDTO.OrderItemDTO(
"SKU-001",
"Product 1",
"image1.jpg",
2,
new BigDecimal("50.00"),
new BigDecimal("100.00")
)
)
);
} else if ("ORD-002".equals(orderNumber)) {
return new OrderDetailDTO(
"2",
"ORD-002",
new BigDecimal("200.00"),
"COMPLETED",
"PAID",
"2024-01-02 12:00:00",
Arrays.asList(
new OrderDetailDTO.OrderItemDTO(
"SKU-002",
"Product 2",
"image2.jpg",
1,
new BigDecimal("200.00"),
new BigDecimal("200.00")
)
)
);
}
return null;
}
}
3.2 创建订单查询控制器
创建一个OrderQueryController
类,用于处理订单查询的HTTP请求:
java
package com.example.redission.controller;
import com.example.redission.dto.OrderDetailDTO;
import com.example.redission.service.OrderQueryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/orders")
public class OrderQueryController {
private static final Logger logger = LoggerFactory.getLogger(OrderQueryController.class);
private final OrderQueryService orderQueryService;
@Autowired
public OrderQueryController(OrderQueryService orderQueryService) {
this.orderQueryService = orderQueryService;
}
/**
* 查询订单信息
*
* @param orderNumber 订单号
* @return 订单信息,如果不存在则返回 null
*/
@GetMapping("/{orderNumber}")
public OrderDetailDTO getOrderDetails(@PathVariable String orderNumber) {
OrderDetailDTO order = orderQueryService.getOrderDetails(orderNumber);
logger.info("Query order {}: result = {}", orderNumber, order);
return order;
}
}
4. 测试与验证
启动应用后,可以通过以下API接口进行测试:
- 查询订单信息:
GET /api/orders/{orderNumber}
示例请求
查询订单信息
GET /api/orders/ORD-001
示例响应
如果订单存在:
json
{
"orderId": "1",
"orderNumber": "ORD-001",
"totalAmount": 100.00,
"orderStatus": "COMPLETED",
"paymentStatus": "PAID",
"createTime": "2024-01-01 12:00:00",
"items": [
{
"skuId": "SKU-001",
"skuName": "Product 1",
"skuImage": "image1.jpg",
"quantity": 2,
"price": 50.00,
"subtotal": 100.00
}
]
}
5. 总结
通过本文的示例,我们展示了如何在Spring Boot中集成Redisson来实现布隆过滤器,并通过订单查询功能展示了其应用。布隆过滤器可以有效地减少数据库查询的次数,提高系统的性能和响应速度。在实际应用中,可以根据业务需求调整布隆过滤器的参数,以达到最佳的性能和误判率平衡。