Redisson分布式锁的概念和使用
- 一 简介
- 1.1 什么是分布式锁?
- 1.2 Redisson分布式锁的原理
- 1.3 Redisson分布式锁的优势
- 1.4 Redisson分布式锁的应用场景
- 二 案例
- 2.1 锁竞争案例
- 2.2 看门狗案例
- 2.3 参考文章
前言
这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。作者:神的孩子都在歌唱
一 简介
1.1 什么是分布式锁?
在分布式系统中,多个服务实例或进程可能会同时访问共享资源(例如数据库、文件等)。为了防止数据竞争或一致性问题,我们需要一种机制来确保在同一时间,只有一个进程能够访问这些资源。这种机制就是分布式锁。
Redisson 是一个支持 Redis 的 Java 客户端,它不仅能提供简单的 Redis 连接,还包括了许多高级功能,如分布式锁、异步任务执行、限流等。Redisson 基于 Redis 来实现分布式锁,具备高效、可靠的特性。
1.2 Redisson分布式锁的原理
Redisson 的分布式锁主要依赖 Redis 的 SETNX
和 EXPIRE
命令来实现。流程如下:
-
获取锁:客户端通过
SETNX
(SET if Not eXists)命令尝试在 Redis 中设置一个键。如果该键不存在,表示没有其他进程持有锁,当前进程即可成功获取锁。 -
锁过期时间:为了避免某个持有锁的进程崩溃而导致锁无法释放,通常会为锁设置一个过期时间(如10秒)。如果超过这个时间锁还没有被释放,Redis 将自动删除该锁,允许其他进程获取。
-
释放锁:当任务完成后,进程会通过
DEL
命令来释放锁,其他进程就可以重新获取锁。 -
可重入锁:Redisson 提供了可重入锁,即同一个线程可以多次获取锁,而不会被锁定阻塞。只有当线程完全释放锁后,其他线程才能获取该锁。
1.3 Redisson分布式锁的优势
- 高效:Redis 基于内存操作,具有极快的响应速度。Redisson 使用 Lua 脚本将获取和释放锁的操作进行原子化操作,避免并发问题。
- 可靠性:Redisson 通过 Redis 的过期机制和 Watchdog(看门狗)机制,确保锁可以自动释放,防止因进程异常退出导致的死锁问题。
- 可扩展性:Redisson 支持 Redis 集群、哨兵模式等多种模式,适用于不同规模的分布式系统。
1.4 Redisson分布式锁的应用场景
-
库存扣减:在电商系统中,当用户发起购买请求时,需要确保在多个并发请求中,库存只能被扣减一次,避免超卖现象。
-
任务调度:在分布式任务调度系统中,需要保证同一时间只有一个服务实例执行某个任务,防止重复执行。
-
分布式事务:在分布式事务中,确保在多个服务之间的一致性操作,分布式锁能够确保只有一个服务能修改某个共享资源。
二 案例
2.1 锁竞争案例
假设我有两个节点可以执行同一种业务,我需求是节点1执行的时候,节点2不能执行。那么就可以使用分布式锁实现,代码如下
引入redisson的依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.35.0</version>
</dependency>
编一个redisson客户端
public class RedissonConfig {
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.1.47:6379").setPassword("xxxx");
return Redisson.create(config);
}
}
节点1
package org.example.demo;
import org.example.tool.RedissonConfig;
import org.redisson.api.RKeys;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* @Author chenyunzhi
* @DATE 2024/9/3 10:01
* @Description:
*/
public class demo1 {
public static void main(String[] args) throws InterruptedException {
RedissonConfig redissonConfig = new RedissonConfig();
RedissonClient redissonClient = redissonConfig.redissonClient();
int i = 0;
Thread.sleep(2000);
while (i < 5) {
try {
Date currentDate = new Date();
System.out.println("当前时间: " + currentDate);
RLock testLock = redissonClient.getLock("testLock");
//尝试获取锁 waitTime(重试等待时间),leaseTime(过期时间),TimeUnit(时间单位)
boolean b = testLock.tryLock(1, 5, TimeUnit.SECONDS);
i++;
if (b) {
try {
Thread.sleep(2000);
System.out.println("执行业务" + i + "次 节点1");
} finally {
testLock.unlock();
}
} else {
System.out.println("获取锁失败 节点1");
}
} catch (Exception e) {
System.out.println("锁中断 节点1" + e.getMessage());
}
}
}
}
节点1完成业务的速度大约2秒钟,循环执行5次任务,如果获取锁失败就不执行,进入下一次执行
节点2
package org.example.demo;
import org.example.tool.RedissonConfig;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* @Author chenyunzhi
* @DATE 2024/9/3 10:01
* @Description:
*/
public class demo2 {
public static void main(String[] args) throws InterruptedException {
RedissonConfig redissonConfig = new RedissonConfig();
RedissonClient redissonClient = redissonConfig.redissonClient();
int i = 0;
while (i < 5) {
try {
Date currentDate = new Date();
System.out.println("当前时间: " + currentDate);
RLock testLock = redissonClient.getLock("testLock");
//尝试获取锁 waitTime(重试等待时间),leaseTime(过期时间),TimeUnit(时间单位)
boolean b = testLock.tryLock(1, 10, TimeUnit.SECONDS);
i++;
if (b) {
try {
Thread.sleep(1000);
System.out.println("执行业务" + i + "次 节点2");
} finally {
testLock.unlock();
}
} else {
System.out.println("获取锁失败 节点2");
}
} catch (Exception e) {
System.out.println("锁中断 节点2" + e.getMessage());
}
}
}
}
节点2完成业务的速度大约1秒钟,循环执行5次任务,如果获取锁失败就不执行,进入下一次执行
然后我们同时允许两个节点行测试
通过上面测试可以发现,在节点1执行业务的时候,节点2获取锁失败了,然后无法执行业务,反之也是如此
2.2 看门狗案例
上面的案例我们为了防止任务死锁,都会给锁都设定了有效时间,可是我们不确定这个任务要执行多久,就会导致任务还没执行完成,锁就先过期了。watchDog(看门狗)的作用是可以确保等待某个节点任务完全执行完成后才去释放锁。
redisson的看门狗底层使用的是setnx加lua脚本实现的,会定期给锁续约,默认是每隔10s续期一次,一次续约30s,其他线程在最大等待时间内自旋,不断尝试获取锁,超过最大等待时间则获取锁失败,设置默认加锁时间的参数是 lockWatchdogTimeout,会和主线程一起销毁。。
注意:
- 看门狗只会在锁没设定过期时间的时候才有效
- 如果任务一直阻塞,那么锁就会一直续期,得不到释放
package org.example.demo;
import org.example.tool.RedissonConfig;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
/**
* @Author chenyunzhi
* @DATE 2024/9/21 11:26
* @Description:
*/
public class demo3 {
public static void main(String[] args) {
RedissonConfig redissonConfig = new RedissonConfig();
RedissonClient redissonClient = redissonConfig.redissonClient();
RLock myLock = redissonClient.getLock("myLock");
if (myLock.tryLock()) {
try {
// 模拟任务执行
System.out.println("执行任务");
Thread.sleep(50000); // 模拟50秒的任务
System.out.println("任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//判断当前线程是否持有锁 isHeldByCurrentThread
if (myLock.isHeldByCurrentThread()) {
myLock.unlock();
System.out.println("释放锁");
}
}
}
redissonClient.shutdown();
}
}
使用myLock.tryLock()不设置过期时间,那么看门狗会默认启动,然后会默认设置30秒的过期时间,每10秒刷新一次过期时间。
2.3 参考文章
github: https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers
redisson中的看门狗机制总结
Redis的分布式锁
redisson watchdog 使用和原理
作者:神的孩子都在歌唱
本人博客:https://blog.csdn.net/weixin_46654114
转载说明:务必注明来源,附带本人博客连接。