在redis分布式锁中,我们一步步的分析,实现了原生的redis分布式锁,但是这样操作实在是麻烦,因此,这里介绍下一个开源的redis分布式锁框架——redisson,看下它是怎么使用的。
org.redisson
redisson
3.12.0
可以配置单机模式、集群模式等等,这里以单机模式为例
package com.example.redis.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author zy
* @version 1.0.0
* @ClassName RedissonConfig.java
* @Description 操作redisson的redissonClient对象
* @createTime 2023/4/10
*/
@Configuration
public class RedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient(){
//创建连接配置
Config config = new Config();
//单机地址,可创建集群服务、哨兵服务、主从服务等等
//注意这里一定要按照格式写,否则报错Redis url should start with redis:// or rediss:// (for SSL connection)
config.useSingleServer().setAddress("redis://ip:port").setPassword("没有密码可以忽略");
//创建redisson实例
RedissonClient redissonClient = Redisson.create(config);
/**
* 集群模式
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://ip1:端口1")
.addNodeAddress("redis://ip1:端口2")
.addNodeAddress("redis://ip1:端口3")
.addNodeAddress("redis://ip2:端口1")
.addNodeAddress("redis://ip2:端口2")
.addNodeAddress("redis://ip2:端口3");
RedissonClient redissonClient = Redisson.create(config);
**/
return redissonClient;
}
}
/**
* 首先,redisson的使用非常简单,我们配置好redisson客户端,然后关注加锁解锁即可
* RLock lock = redisson.getLock("lock name");
* lock.lock();
* lock.unlock();
* 几个声明点:
* 1、redisson所有指令都通过lua脚本执行(我们在原生的redis分布式锁中也用到lua脚本加解锁)
* 2、redisson设置一个key的默认过期时间为30s(那么如果业务执行时间超过30s怎么办?能否传入自定义时间?)
* 2.1、redisson有一个锁自动续期机制(看门狗机制),只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s续成满时间30s
* 这里要注意的是使用默认锁时间
* 2.2、当然可以自定义时间,lock.lock(10, TimeUnit.SECONDS);但是需要注意自定义时间,没有看门狗机制
* 3、看门狗机制会有死锁吗?如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期了
*/
/**
* 可重入锁实现
* @return
*/
@RequestMapping("/reenterLock")
public String reenterLock(){
//调用getLock获取一把锁,相同的锁名字就是同一把锁
RLock lock = redissonClient.getLock("my-reenterLock");
//加锁
lock.lock();
try {
System.out.println("加锁成功,开始执行业务,线程ID为:"+Thread.currentThread().getId());
Thread.sleep(35*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//解锁
lock.unlock();
System.out.println("业务执行完毕,释放锁成功,线程ID为:"+Thread.currentThread().getId());
}
return "hello";
}
/**
* 实现读写锁:读写锁保证一定能读取到最新数据,写数据期间是一个独占锁(互斥、排他)
* 核心思想:读锁是一个共享锁,写锁是一个独占锁,写锁没释放,读锁就一直等待
* 几种情况:
* 读+读:相当于无锁,可以并发读,都会加锁成功
* 写+读:等待写锁释放
* 写+写:阻塞
* 读+写:需要等待读锁释放才能写
* 只要有写锁,就要等待
* 通过接口多次请求及不同的组合方式测试上述情况
* @return
*/
@RequestMapping("/write")
public String write(){
System.out.println("写锁业务.....");
//声明同一把锁
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
//读写变量
String s = "";
//声明写锁
RLock writeLock = lock.writeLock();
try {
//写数据加写锁
writeLock.lock();
System.out.println("写锁加锁成功,线程ID为:"+Thread.currentThread().getId());
//修改变量,模拟业务
s= UUID.randomUUID().toString();
Thread.sleep(35*1000);
//放入缓存,为了能读写同一个地方
redisTemplate.opsForValue().set("read-write-value",s);
}catch (Exception e){
e.printStackTrace();
}finally {
writeLock.unlock();
System.out.println("写锁释放成功,线程ID为:"+Thread.currentThread().getId());
}
return s;
}
@RequestMapping("read")
public String read(){
System.out.println("读锁业务......");
//声明同一把锁
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
String s = "";
//声明读锁
RLock readLock = lock.readLock();
try {
//读数据加读锁
readLock.lock();
System.out.println("读锁加锁成功,线程ID为:"+Thread.currentThread().getId());
//模拟业务
Thread.sleep(35*1000);
s = String.valueOf(redisTemplate.opsForValue().get("read-write-value"));
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
System.out.println("读锁解锁成功,线程ID为:"+Thread.currentThread().getId());
}
return s;
}
/**
* 实现信号量:分布式信号量,用来做限流
* 场景:停车问题,一共3个车位,每次来车判断是否有车位,有就停,没有就开走
* 分为停车和取车两个方法
* @return
*/
@RequestMapping("/initPark")
public void initPark(){
//初始化车位
redisTemplate.opsForValue().set("park",3);
}
@RequestMapping("/park")
public String park() throws InterruptedException {
RSemaphore park = redissonClient.getSemaphore("park");
//获取一个信号量,获取不到会一直等待
//park.acquire();
boolean empty = park.tryAcquire();//尝试获取车位,没有就离开
return "可以停车吗=>"+empty;
}
@RequestMapping("/move")
public String move(){
RSemaphore park = redissonClient.getSemaphore("park");
park.release();//信号量释放,即释放车位
return "ok";
}
/**
* 实现CountDownLatch(闭锁)
* 场景:一个班有五个人,等人走完了才锁门
* 调用锁门方法,如果一直有人,会等待,直到所有人走完,才执行完毕
*/
@RequestMapping("/lockDoor")
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redissonClient.getCountDownLatch("door");
door.trySetCount(5);
door.await();//等待所有人走完
return "人都走了,锁门......";
}
@RequestMapping("/go")
public String go(){
RCountDownLatch door = redissonClient.getCountDownLatch("door");
door.countDown();
return "还剩"+door.getCount()+"个人没走";
}
@RequestMapping("/mutiLock")
public String mutiLock(){
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RLock lock = redissonClient.getMultiLock(lock1,lock2,lock3);
boolean flag = lock.tryLock();
if(flag){
try{
System.out.println("联锁加锁成功");
//业务
}catch (Exception e){
}finally {
//释放锁
lock.unlock();
}
}
return "ok";
}
这篇文章只要介绍redisson的使用,实现了可重入锁、信号量、读写锁等常见的锁,redisson的强大不只这些,还有公平锁等实现,这篇文章主要贴近应用,如果需要看原理,还需深入学习,或者看下前文从Reentrantlock看AQS独占式锁原理、Condition接口在AQS中实现的原理分析、Semaphore浅析、ReentrantLock的源码分析,对理解源码有些帮助。