基于Redis的Redisson分布式锁实现方案
基于redis的分布式锁(redisson)
1、分布式锁介绍:
但是这里的都只是单机应用,即在同一个JVM中;然后随着业务发展、微服务化,一个应用需要部署到多台服务器上然后做负载均衡,大概的架构图如下:
在上图可以看到,变量A在JVM1、JVM2、JVM3三个JVM内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象),如果我们不加任何控制的话,变量A同进都会在JVM分配一块内存,三个请求发过来同时对这个变量进行操作,显然结果不是我们想要的。
如果我们业务中存在这样的场景的话,就需要找到一种方法来解决。
为了保证一个方法或属性在高并发的情况下同一时间只能被同一个线程执行,在传统单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock
或Synchronized
)进行互斥控制。但是,随之业务发展的需要,原单机部署的系统演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同的机器上,这将原来的单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。
为了解决这个问题,就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
分布式锁应该具备哪些条件
- 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
- 高可用、高性能的获取锁与释放锁;
- 具备可重入特性;
- 具备锁失效机制、防止死锁;
- 具备非阻塞锁特性,即没有获取到锁直接返回获取锁失败;
2、分布式锁的实现方式
目前几乎所有大型网站及应用都是分布式部署,分布式场景中的数据一致性问题一直是一个比较重要的话题,分布式的CAP理论告诉我们任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项
。
一般情况下,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性
,只要这个最终时间是在用户可以接受的范围内即可。
在很多时候,为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一信方法在同一时间内只能被同一个线程执行。
关于分布式锁的实现,目前主流方案有以下三类:
1、基于数据库的乐观锁;
2、基于redis实现的锁服务;
3、基于zookeeper的实现;
3、这里主要介绍基于Redis的分布式锁的实现:
引入依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.1</version>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3、封装工具类:
package com.dw.study.util;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.TimeUnit;
/**
* @Author dw
* @ClassName RedissionLockUtil
* @Description 基于Redisson的分布式锁实现
* @Date 2020/5/24 0:36
* @Version 1.0
*/
@componet
public class RedissionLockUtil {
@Autowired
private RedissonClient redissonClient;
/**
* 加锁
* @param lockKey
* @return
*/
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
/**
* 加锁,过期自动释放
* @param lockKey
* @param leaseTime 自动释放锁时间
* @return
*/
public RLock lock(String lockKey, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return lock;
}
/**
* 加锁,过期自动释放,时间单位传入
* @param lockKey
* @param unit 时间单位
* @param leaseTime 上锁后自动释放时间
* @return
*/
public RLock lock(String lockKey, TimeUnit unit, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, unit);
return lock;
}
/**
* 尝试获取锁
* @param lockKey
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放时间
* @return
*/
public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
/**
* 尝试获取锁
* @param lockKey
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public boolean tryLock(String lockKey, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
/**
* 释放锁
* @param lockKey
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
/**
* 释放锁
* @param lock
*/
public void unlock(RLock lock) {
lock.unlock();
}
}
使用示例:
@Autowired
private RedissionLockUtil redissionLockUtil;
String lockKey = "only_id";
try{
//超过2S自动释放锁
redissionLockUtil.lock(lockKey, 2L);
//业务处理
} finally{
redissionLockUtil.unlock(lockKey); //释放锁
}
4、读写锁
@Autowired
RedissonClient redisson;
@Autowired
RedisTemplate redisTemplate;
//写锁保证一定能读到最新数据,修改期间,写锁是一个排他锁(互诉锁,独享锁)。读锁是一个共享锁
//写锁没释放,读就必须等待
//写 + 读 (写的时候进行读操作):等待写锁释放
//写 + 写 (写的时候进行写操作):阻塞方式
//读 + 写 (读的时候进行写操作):等待读锁释放
//读 + 读 :相当于无锁,并发读,只会在redis中记录好,所有当前的读锁。他们都会同时加锁成功。
//总结:只要有写的存在,就必须等待前面的锁释放。
@ResponseBody
@RequestMapping("/write")
public String writeLock(){
RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
String s = "";
RLock rLock = lock.writeLock();
try {
// 改数据加写锁,读数据加读锁
rLock.lock();
s = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("writerValue",s);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
@ResponseBody
@RequestMapping("/read")
public String readLock(){
RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
RLock rLock = lock.readLock();
String s = "";
try {
//加读锁
rLock.lock();
s = redisTemplate.opsForValue().get("writerValue").toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
5、闭锁
/**
* 下班了,关门回家
* 1、部门没人了
* 2、5个部门全部走完,锁门回家
* @return
* @throws InterruptedException
*/
@ResponseBody
@RequestMapping("/lockDoor")
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await(); // 等待闭锁都完成
return "下班了。。。。";
}
@ResponseBody
@RequestMapping("/gogogo/{id}")
public String gogogo(@PathVariable("id") String id){
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown(); //计数减1
return id + "部门下班了";
}
6、信号量
注:信号量也可以用作分布式限流
/**
* 车库停车(信号量)
* 3车位
* 信号量也可以用作分布式限流
* @return
*/
@ResponseBody
@RequestMapping("/park")
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
//park.acquire(); //获取一个信号,获取一个值,占一个车位(阻塞方式)
boolean b = park.tryAcquire();//直接运行之后的代码,非阻塞
if (b){
//执行业务
}
return "ok";
}
@ResponseBody
@RequestMapping("/gogo")
public String gogo(){
RSemaphore park = redisson.getSemaphore("park");
park.release(); //释放一个车位,车开走了
return "ok";
}