Redisson概述
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
Redisson底层采用的是Netty 框架。支持Redis 2.8以上版本,支持Java1.6+以上版本。
一句话说Redisson就是redis的java客户端,底层通信使用Netty,提供了众多redis场景的API,比如分布式锁等等。
Springboot整合redisson:
这里整合方式不是重点,有多种整合方式和模式。
有配置文件Yml整合,编程式方法整合。
有整合单节点redis的方法、整合哨兵模式的方法、整合集群方法。
这里就整合单节点redis的方法。其他方法可以看GitHub的文档,有中文的。
https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
引入依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.3</version>
</dependency>
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
//配置redis的服务地址,地址要以redis://ip:port 如果是安全连接ssl,就要rediss://ip:port
config.useSingleServer().setAddress("redis://192.168.18.140:6379");
//创建一个RedissonClient,redisson的主要操作都是通过这个客户端完成。
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
//整合完成
可重入锁
@Test
void testRedissonReentrantLock(){
//获取RLock对象,输入锁的名字,只有相同名字的锁才是同一把锁。
RLock rLock = redissonClient.getLock("redissonReentrantLock");
//上锁,该方法会阻塞当前线程,直到获取锁才往下走。
rLock.lock();
//下面方法执行时设置锁的超时时间为十秒,是另一个上锁重载方法,该方法会跳过看门狗
//机制,即使锁超时后业务代码没有执行完成,依旧会释放锁,而不会对锁进行续期。
rLock.lock(10,TimeUnit.SECONDS);
try {
System.out.println("获取分布式可重入锁成功");
//执行业务代码
try {
TimeUnit.SECONDS.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
//释放锁
rLock.unlock();
}
}
@Test
void testRedissonReentrantTryLock(){
//获取RLock对象,输入锁的名字,只有相同名字的锁才是同一把锁。
RLock rLock = redissonClient.getLock("redissonReentrantLock");
boolean succeed = false;
try {
//尝试获取锁,第一个参数是等待时间,第二个参数是锁的超时时间,
//该方法尝试获取锁,不一定要获取成功,设置一个等待时间,线程会最多阻塞住该时间
//假如在等待时间内还是没有获取锁的话,线程就会不阻塞了,继续往下走,只是没有获取到
//锁succeed 会为false。如果获取到了锁,succeed会为true。
//如果在等待时间内获取到了锁,线程就会往下执行。
succeed = rLock.tryLock(100, 10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (succeed){
try {
System.out.println("获取分布式可重入锁成功");
//执行业务代码
try {
TimeUnit.SECONDS.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
//释放锁
rLock.unlock();
}
}
}
看门狗机制
默认的锁超时时间是30秒超时,但是Redisson提供了一个看门狗机制,锁的默认超时时间就是这个看门狗时间,默认30秒,可以通过Config.lockWatchdogTimeout来指定。
看门狗机制流程是这样的:
- 线程获取分布式锁,锁的默认超时时间是30秒。
- 但是有可能业务代码比这个锁的时间要长,为了避免业务代码没执行完成锁释放了,看门狗机制默认会在看门狗时间的三分之一时对锁的时间进行重置,比如看门狗时间是30秒,业务代码执行50秒,那么看门狗机制就每个10秒对锁进行一次超时重置回30秒,直到业务代码执行完成释放锁为止。
- Redisson连接出现异常断开后,看门狗机制也就失效了,那么锁会在超时后释放,不再进行重置。
公平锁
公平锁是指获取锁的顺序是按照请求的发起顺序决定的,公平的,类似把获取锁的线程放进一个先进先出的队列,当锁释放后,由队列弹出一个线程来获取成功,获取的线程是有序的。
而非公平锁是一旦锁释放了,所以的线程都会被唤醒来争抢锁。
使用方式:
//使用getFairLock方法获取,剩下的操作方式跟上面的可重入锁一样。
RLock rLock = redissonClient.getFairLock("redissonReentrantFairLock");
读写锁
读写锁的特点:
读锁又称共享锁,多个读锁可以共同访问同一资源。
写锁又称排他锁,某资源被上锁后,其他线程(进程)就不能访问该资源,直到释放锁。
读写锁总是成对出现的,确保读锁读到的数据时最新的。
- 获取到读锁后,其他读锁可以访问该资源,写锁不能访问该资源。
- 获取到写锁后,其他读写锁都不能访问该资源。
读写锁的原理实现在博主其他文章里有介绍,不过是使用Zookeeper实现的。
@Test
//读锁
public void testReadLock(){
//获取读写锁RReadWriteLock ,读锁跟写锁是成对出现的,锁定的资源也是一样的,所以锁的名称要一样
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("r-w-lock");
//获取一把读锁
RLock readLock = readWriteLock.readLock();
//其余操作就跟可重入锁一样了
readLock.lock();
try {
System.out.println("获取了读锁");
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
//释放锁
readLock.unlock();
}
}
@Test
//写锁
public void testWriteLock(){
//锁名要与读锁一致,因为是成对出现
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("r-w-lock");
RLock writeLock = readWriteLock.writeLock();
writeLock.lock();
try {
System.out.println("获取了写锁");
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
writeLock.unlock();
}
}
可以自己测试,当读锁状态时,其他读线程可以进入访问,写线程不可以,当写锁状态时,读写锁都不能进入。
闭锁
Redisson提供了分布式闭锁的API,与JUC的本地闭锁CountdownLatch提供的功能类似。但是他是分布式的闭锁。
@Test
//上锁
public void testCountDownLatchLock(){
RCountDownLatch rCountDownLatch = redissonClient.getCountDownLatch("countDownLatch");
//设置计数器为10,当减到0时取消阻塞,执行下面代码。
rCountDownLatch.trySetCount(10);
try {
rCountDownLatch.await();
//执行业务流程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testCountDownLatchLockRelease(){
RCountDownLatch rCountDownLatch = redissonClient.getCountDownLatch("countDownLatch");
//减一
rCountDownLatch.countDown();
}
信号量
信号量就类似于一个信号数量。信号数量有剩时,就能获取到信号来执行下面的操作。
比如一个信号量有5个信号,就类比停车场有5个车位,信号量就是停车场,信号就是停车位。
当一个线程进来获取信号,信号量就减一,当一个线程释放信号,信号量就加一。当信号量为0时,就获取不到信号,线程阻塞着,直到有信号量或者超时退出。
//要预先在redis中设置一个key为semaphore,值是一个大于0的数字才可以。
public void testAcquireSemaphore() throws InterruptedException {
//获取信号量
RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
//获取信号,信号量减一,会阻塞当前线程,知道获取到信号为止
semaphore.acquire();
System.out.println("获取到了信号");
}
public void testReleaseSemaphore(){
//获取信号量
RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
//释放信号,信号量加一
semaphore.release();
System.out.println("获取到了信号");
}
使用信号量来做简单的限流
public void testLimitFlow() {
//获取信号量,可以将信号量设置为1000,代表当前系统的当前业务限流为1000,同时只能有1000并发,其他进行服务的降级
RSemaphore semaphore = redissonClient.getSemaphore("limitFlow");
//尝试获取信号,信号量减一,会阻塞当前线程,直到获取到信号或者超时5秒返回,超时就返回false,获取到信号就返回true
boolean succeed = false try {
succeed = semaphore.tryAcquire(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
if (succeed){
try {
System.out.println("获取到了信号,执行业务逻辑");
}finally {
//释放信号
semaphore.release();
}
}else {
//没有获取到信号量,直接返回,类似服务降级
}
}
可过期性信号量
可过期性信号量的意思是获取的信号可以设置过期时间,如果因为异常或者业务逻辑时间长导致超时后,信号自动释放。
public void testExpireSemaphore() throws InterruptedException {
RPermitExpirableSemaphore rPermitExpirableSemaphore = redissonClient.getPermitExpirableSemaphore("ExpirableSemaphore");
//获取信号,并且设置信号如果3秒内得不到释放就会自动释放,返回值是信号Id。
String permitId = rPermitExpirableSemaphore.acquire(3, TimeUnit.SECONDS);
try {
System.out.println("获取到了可过期信号");
System.out.println("执行业务逻辑");
}finally {
//释放信号
rPermitExpirableSemaphore.release(permitId);
}
}