Redisson
1.介绍
Redisson - 是一个高级的分布式协调Redis客服端 , 专注于分布式系统开发,让用户可以在分布式系统中很方便的去使用Redis。
2.相关使用
1.加锁
//底层是lua脚本保证了加锁的原子性
// 一直等待获取锁,直到获取到锁为止! 默认锁的存活时间为30s
lock.lock();
// 一直等待获取锁,并且显式的指定锁的过期时间
lock.lock(10 , TimeUnit.SECONDS);
// 尝试获取一次锁,如果可以获取到锁就返回true,否则返回false, 默认情况下锁的过期时间为30s
lock.tryLock();
// 尝试获取一次锁,指定加锁的等待超时时间为3s, 如果在3s内获取到了锁,那么此时就返回true,否则返回false, 默认情况下锁的过期 时间为30s
boolean tryLock = lock.tryLock(3, 20, TimeUnit.SECONDS);
//通过如下方式获取的锁是可重入锁:
RLock lock = redissonClient.getLock(”redisson-lock”);
2.释放锁
加解锁必须成对出现
🌟面试题
1.在你的项目开发的过程中有没有遇到过一些技术难点,你是怎么解决的?
有,在我们的系统使用到了Redisson框架,主要用的就是它里面分布式锁的功能,在Redssion框
架中分布式锁的实现是存在续期机制的,续期机制底层是通过一个异步线程进行执行的。由一次我
们在写代码的时候由于释放锁的代码没有写到finally语句块中,业务代码执行的时候产生了异常,
锁的续期代码照样执行,就导致其他的线程获取不到分布式锁就出现了死锁现象。
2.🌟死锁产生的条件,如何避免?
-
互斥条件
-
占有且等待
-
不可抢占
-
循环等待
死锁一旦发生,其实基本上就很难人为干预解决他,所有我们只能尽可能的规避他上面提到的四个
条件,只要同时满足了就会触发死锁,我们只需打破其中任意一条,死锁自然也就不会存在了
举例说明:
第一条:互斥条件这个作为锁所必须的条件,无法干预
第二条:破坏占有并等待==>可以一次性申清所需的所有资源此时就不存在等待的问题
第三条:当其中一个线程再去申请资源的时候,如果申请不到
第四条:破坏循环等待==>给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行
3.🌟如何检测(死锁)出来的?
检测死锁的三种方法
-
使用图形化工具jconsole.exe
-
jps -l 查看线程信息 ------> 使用 jstack (id)
-
使用jprofiler工具
/**
* @author: Niimp
* @date: 2023/8/2 14:46
* @statement: 测试代码(死锁)
* 检查死锁的三种方法:
* JDK自带:
* 1.使用图形化工具jconsole.exe
* 2.jps -l 查看线程信息
* 使用 jstack (id)
* 3.使用jprofiler工具
*
*/
package com.niimpday1;
import java.util.concurrent.TimeUnit;
class DeadLock {
final Object resource1 = new Object();
final Object resource2 = new Object();
public void DeadLock(){
new Thread(() -> {
synchronized (resource1){
System.out.println(Thread.currentThread().getName() + "获取resource1");
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "尝试获取resource2");
synchronized (resource2){
System.out.println(Thread.currentThread().getName() + "获取resource2");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "A").start();
new Thread(() -> {
synchronized (resource2){
System.out.println(Thread.currentThread().getName() + "获取resource2");
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "尝试获取resource1");
synchronized (resource1){
System.out.println(Thread.currentThread().getName() + "获取resource1");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "B").start();
}
}
public class Test9 {
public static void main(String[] args) {
new DeadLock().DeadLock();
}
}
3.锁续期
-
当用户没有指定锁的超时时间的时候,那么此时Redssion框架会自动给锁进行续期。
-
续期规则:当锁的经过了设置时间的1/3的时候,会自动续期到默认的30s(自定义设置锁的过期时间会使锁续期机制失效)
-
续期原理:通过一个定时任务进行实现,底层通过一个异步线程完成锁的续期
4.信号量
进行限流
RSemaphore semaphore = null ;
@PostConstruct
public void init() {
semaphore = redissonClient.getSemaphore("test-semaphore");
semaphore.addPermits(2); // 分配两个许可证
}
@GetMapping(value = "/semaphore")
public Result semaphore() throws InterruptedException {
semaphore.acquire(); // 申请许可证
log.info(Thread.currentThread().getName() + "----> 申请到了一个许可证...");
Thread.sleep(500);
semaphore.release();
log.info(Thread.currentThread().getName() + "----> 归还了一个许可证....");
return Result.ok() ;
}
5.其他锁
//1、公平锁:
RLock lock = redisson.getFairLock(”myLock”);
//2、读写锁:
RReadWriteLock rwlock = redisson.getReadWriteLock(”myLock”);
//读写锁的特性:读读兼容、读写互斥、写写互斥、写读互斥
6.分布式布隆过滤器
本地布隆过滤器的弊端:集群环境太浪费系统资源、集群环境也不容易对布隆过滤器进行维护
1.Redssion中提供的分布式布隆过滤器的使用
// 获取一个分布式的布隆过滤器(RedissonClient)
RBloomFilter<V> getBloomFilter(String name);
// 初始化分布式的布隆过滤器(RBloomFilter)
boolean tryInit(long expectedInsertions, double falseProbability);
// 判断布隆过滤器中是否存在数据(RBloomFilter)
boolean contains(T object);
// 判断布隆过滤器是否存在(RBloomFilter)
boolean isExists();
// 向布隆过滤器中进行数据的添加
boolean add(T object);
代码实现:
// com.atguigu.gmall.product.service.impl.SkuInfoServiceImpl
@Autowired
private RedissonClient redissonClient ;
// 将BloomFilter过滤器的初始化动作放到service-product中更为合适,不用发起远程调用
@PostConstruct
public void initBloomFilter() {
// 分布式布隆过滤器的使用
RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(GmallConstant.DIS_BLOOM_FILTER);
if(!bloomFilter.isExists()) {
bloomFilter.tryInit(1000000 , 0.00001) ;
List<Long> skuIds = skuInfoMapper.findAllSkuIds() ;
skuIds.forEach( skuId -> {
bloomFilter.add(skuId) ;
});
log.info("分布式布隆过滤器,判断49号商品是否存在:" + bloomFilter.contains(49L) + "判断99号商品是否存在: " + bloomFilter.contains(99L));
}
}
// 添加bloomFilter常量值到GmallConstant中
public static final String DIS_BLOOM_FILTER = "sku-bloom-filter" ;
2. 布隆过滤器重置
1.数据库中的数据发送变更以后bloomFilter是否需要变更?
-
新增数据:直接向bloom过滤器中添加新的数据即可
-
修改数据: 不用对bloomFilter进行变更(布隆过滤其中存储的是id)
-
删除数据:需要重置bloom过滤器的值(删除原来的bloom过滤器创建新的bloom过滤器)
2.🌟布隆过滤器重置步骤
-
创建一个新的布隆过滤器 (sku:bloom:filter:new)
-
删除之前的布隆过滤器(sku:bloom:filter)
-
对新创建的布隆过滤器进行重命名,把新布隆过滤器的名称改成之前的布隆过滤器的名称
需要保证:删除和重命操作的原子性(使用lua脚本)
3.🌟布隆过滤器重置时机
看业务对数据的时效性要求
1.业务对数据要求高时效性:需要在删除数据的时候调用重置布隆过滤器的方法完成布隆过滤器的
重置,虽然会浪费一些性能,但也得这么做
2.业务对数据的时效性要求不高:使用定时任务实现布隆过滤器的重置
1.定时任务
1、定时任务触发(spring task)
// com.atguigu.gmall.product.task
@Service
@Slf4j
public class ResetBloomFilterTimeTask {
@Autowired
private BloomFilterService bloomFilterService ;
@Scheduled(cron = "0 0 3 */7 * ?")
public void resetBloomFilterTimeTask() {
log.info("重置bloom过滤器定时任务执行了....");
bloomFilterService.resetBloomFilter();
}
}
// 启动类添加@EnableScheduling注解
cron表达式:命令定时执行方式_云服务器 ECS-阿里云帮助中心
2、提供一个接口,在后台开放调用该接口的功能
// com.atguigu.gmall.product.controller
@RestController
@RequestMapping(value = "/admin/product")
public class BloomFilterController {
@Autowired
private BloomFilterService bloomFilterService ;
@GetMapping(value = "/resetBloomFilter")
public Result resetBloomFilter() {
bloomFilterService.resetBloomFilter();
return Result.ok("重置bloomfilter成功") ;
}
}