Redisson分布式锁
前言
在redis分布式锁中,我们一步步的分析,实现了原生的redis分布式锁,但是这样操作实在是麻烦,因此,这里介绍下一个开源的redis分布式锁框架——redisson,看下它是怎么使用的。
一、引入&配置
1、项目中引入redisson的依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.12.0</version></dependency>
2、配置redisson
可以配置单机模式、集群模式等等,这里以单机模式为例
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实现几种锁
1、可重入锁
/* 首先,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";}
2、读写锁
/* 实现读写锁:读写锁保证一定能读取到最新数据,写数据期间是一个独占锁(互斥、排他)* 核心思想:读锁是一个共享锁,写锁是一个独占锁,写锁没释放,读锁就一直等待* 几种情况:* 读+读:相当于无锁,可以并发读,都会加锁成功* 写+读:等待写锁释放* 写+写:阻塞* 读+写:需要等待读锁释放才能写* 只要有写锁,就要等待* 通过接口多次请求及不同的组合方式测试上述情况* @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、信号量
/* 实现信号量:分布式信号量,用来做限流* 场景:停车问题,一共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";}
4、闭锁
/* 实现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()+"个人没走";}
5、联锁
@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的源码分析,对理解源码有些帮助。