Redission分布式锁、WatchDog续约、布隆过滤器
参考资料:
参考视频
参考博客
学习笔记及参考demo
概述:
首先需要搭建SpringBoot框架,具体参照本人前面的博客。
Redission分布式锁:
依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.21.3</version></dependency>
添加配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.redisson.config.Config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient(){// 配置Config config = new Config();config.useSingleServer().setAddress("redis://192.168.154.150:6379").setDatabase(0)//setAddress("redis://127.0.0.1:6379");//.setPassword("password");// 创建RedissonClient对象return Redisson.create(config);}
}
控制层
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;@RestController
@Slf4j
public class ServerController {@Autowiredprivate RedissonClient redissonClient;/*** 模拟一个服务获取分布式锁 并且更新数据* @param leaseTime 锁施放时间 -1 表示开启watchdog 自动续约 其他的时间为锁自动释放时间* @return*/@RequestMapping("firstRequset")public ResponseEntity<String> firstRequset(@RequestParam("leaseTime")Long leaseTime){try {//获取锁(可重入),指定锁的名称/*** 在 Redisson 中,锁的名称是唯一标识锁的依据,* 因此,只要你在 Redisson 中使用 getLock 方法并传入锁的名称,* Redisson 会在 Redis 中为该名称创建一个锁。你无需手动创建锁,只要使用时保证锁名称唯一且一致即可。** 锁的创建:** Redisson 使用的是 Redis 中的 SETNX(set if not exists)命令来实现分布式锁的原理,* 当你使用 getLock("anyLock") 时,它会在 Redis 中设置一个与名称对应的键(如 anyLock),* 该键的过期时间为你设定的自动释放时间。只要该键存在,其他客户端就无法获得锁。*/RLock lock = redissonClient.getLock("anyLock");/*** 尝试获取锁,参数分别是:* 获取锁的最大等待时间(期间会重试),* 锁自动释放时间,-1 会开启watchdog* 时间单位*/boolean isLock = lock.tryLock(1,leaseTime, TimeUnit.SECONDS);//判断获取锁成功if(isLock){log.info("服务器1-获取锁成功,{}",leaseTime==-1L?"开启自动续约状态":"锁自动释放时间:"+leaseTime);try{// 执行业务Thread.sleep(50_000);log.info("服务器1-执行业务-进行重要数据库字段计算更新");}finally{//释放锁try {lock.unlock();log.info("服务器1-释放锁成功");} catch (Exception e) {log.info("服务器1-释放锁异常,锁已经过期,并且未续约:{}",e);}return new ResponseEntity<String>("执行成功",HttpStatus.OK);}}else{log.info("服务器1-未获取到锁");return new ResponseEntity<String>("未获取到锁",HttpStatus.OK);}}catch (Exception e){e.printStackTrace();}return new ResponseEntity<String>("执行失败",HttpStatus.OK);}// 模拟一个服务获取分布式锁 并且更新数据@RequestMapping("secondRequset")public ResponseEntity<String> secondRequset(@RequestParam("leaseTime")Long leaseTime){try {//获取锁(可重入),指定锁的名称RLock lock = redissonClient.getLock("anyLock");//尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位boolean isLock = lock.tryLock(1,10, TimeUnit.SECONDS);//判断获取锁成功if(isLock){try{log.info("服务器2-获取锁成功,{}",leaseTime==-1L?"开启自动续约状态":"锁自动释放时间:"+leaseTime);//执行业务Thread.sleep(1_000);log.info("服务器2-执行业务-进行重要数据库字段计算更新");}finally{//释放锁try {lock.unlock();log.info("服务器2-释放锁成功");} catch (Exception e) {log.info("服务器2-释放锁异常,锁已经过期,并且未续约:{}",e);}return new ResponseEntity<String>("执行成功",HttpStatus.OK);}}else{log.info("未获取到锁");return new ResponseEntity<String>("未获取到锁",HttpStatus.OK);}}catch (Exception e){e.printStackTrace();}return new ResponseEntity<String>("执行失败",HttpStatus.OK);}
}
WatchDog续约
只需要将参数leaseTime设置为-1,系统就会默认开启,分布式锁无限续约,直到业务执行完成。
布隆过滤器:
依赖
<!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
YAML配置
server:port: 8080
spring:redis:host: 192.168.154.150port: 6379password: nulldatabase: 0timeout: 10000jedis:pool:max-active: 10max-idle: 5min-idle: 1cache:type: redis
配置
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import java.io.IOException;
import java.nio.charset.StandardCharsets;@Service
@Slf4j
public class BloomFilterService {private static final String BLOOM_NAME = "my_bloom_filter";@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate RedisTemplate<String, String> redisTemplate;@PostConstructpublic void initOnStartup() {initBloomFilter();}public void initBloomFilter() {RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(BLOOM_NAME);// 假设你只有 100W 数据bloomFilter.tryInit(1_000_000L, 0.01); // 1百万,误判率 1%// 全量扫描 Redis keyScanOptions options = ScanOptions.scanOptions().count(100000).match("*").build();try (Cursor<byte[]> cursor = redisTemplate.getConnectionFactory().getConnection().scan(options)) {while (cursor.hasNext()) {String key = new String(cursor.next(), StandardCharsets.UTF_8);bloomFilter.add(key);}} catch (IOException e) {throw new RuntimeException("初始化布隆过滤器失败", e);}}/*** 增量更新:新增数据时加入布隆过滤器*/public void add(String key) {log.info("新增数据: " + key);RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(BLOOM_NAME);bloomFilter.add(key);}/*** 查询是否存在*/public boolean mightContain(String key) {log.info("查询数据: " + key);RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(BLOOM_NAME);return bloomFilter.contains(key);}
}
布隆过滤器的一些使用方式
import com.example.springbootredisdemo.config.bloom.BloomFilterService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/bloom")
@Slf4j
public class BloomController {@Autowiredprivate BloomFilterService bloomFilterService;// 新增数据@PostMapping("/add")public String add(@RequestParam String key) throws Exception{Thread.sleep(1000);log.info("新增数据完成: " + key);bloomFilterService.add(key);return "已添加: " + key;}// 查询@GetMapping("/check")public String check(@RequestParam String key) throws Exception{Thread.sleep(1000);log.info("查询数据: " + key);boolean exist = bloomFilterService.mightContain(key);return exist ? "可能存在: " + key : "不存在: " + key;}
}
布隆过滤器的其他方式
Guava过滤器(推荐)
依赖
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>32.1.2-jre</version></dependency>
代码
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;public class GuavaBloomFilterDemo {public static void main(String[] args) {// 预计插入数据量int expectedInsertions = 1000;// 误判率double fpp = 0.01; // 1%// 创建布隆过滤器BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(java.nio.charset.StandardCharsets.UTF_8), // 字符串的 FunnelexpectedInsertions,fpp);// 添加元素bloomFilter.put("123");bloomFilter.put("abc2113");bloomFilter.put("abc2413");// 判断元素是否存在(可能存在误判)System.out.println(bloomFilter.mightContain("123")); // trueSystem.out.println(bloomFilter.mightContain("abc21313")); // false (可能存在误判)}
}
Hutool过滤器
依赖
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version></dependency>
代码
import cn.hutool.bloomfilter.BitMapBloomFilter;
import cn.hutool.bloomfilter.BloomFilter;
import cn.hutool.bloomfilter.filter.DefaultFilter;
import cn.hutool.bloomfilter.filter.ELFFilter;
import cn.hutool.bloomfilter.filter.JSFilter;public class HutoolBloomExample {public static void main(String[] args) {BloomFilter[] filters = new BloomFilter[]{new DefaultFilter(10*32),new ELFFilter(10*32),new JSFilter(10*32)};BitMapBloomFilter bloomFilter = new BitMapBloomFilter(10, filters);// 添加数据bloomFilter.add("apple");bloomFilter.add("banana");bloomFilter.add("orange");// 查询System.out.println(bloomFilter.contains("apple")); // trueSystem.out.println(bloomFilter.contains("grape")); // false (极少可能 true)}
}