文章目录
- 前言
- 一、分布式锁的基本概念
-
- [1.1 什么是分布式锁](#1.1 什么是分布式锁)
- [1.2 为什么选择Redisson](#1.2 为什么选择Redisson)
- 二、Redisson分布式锁的使用
-
- [2.1 基本使用示例](#2.1 基本使用示例)
- [2.2 可重入锁示例](#2.2 可重入锁示例)
- [2.3 看门狗机制的自动续期](#2.3 看门狗机制的自动续期)
- 三、底层原理深度分析
-
- [3.1 加锁原理](#3.1 加锁原理)
- [3.2 解锁原理](#3.2 解锁原理)
- [3.3 看门狗(WatchDog)机制](#3.3 看门狗(WatchDog)机制)
-
- [3.3.1 看门狗的工作原理](#3.3.1 看门狗的工作原理)
- [3.3.2 续期脚本](#3.3.2 续期脚本)
- [3.4 可重入实现原理](#3.4 可重入实现原理)
- 四、总结
前言
在微服务架构和分布式系统中,分布式锁是保证数据一致性的重要手段。Redis作为高性能的内存数据库,天然适合实现分布式锁。而Redisson作为Redis的Java客户端,不仅提供了完善的分布式锁实现,还引入了看门狗(WatchDog)机制来解决锁续期问题。
一、分布式锁的基本概念
1.1 什么是分布式锁
分布式锁是在分布式环境下,多个进程或线程对共享资源进行互斥访问的一种机制。它需要满足以下特性:
- 互斥性:同一时刻只能有一个进程持有锁
- 可重入性:同一线程可以多次获取同一把锁
- 阻塞与非阻塞:获取不到锁时的处理策略
- 容错性:具备自动释放锁的能力
1.2 为什么选择Redisson
相比于直接使用Redis命令实现分布式锁,Redisson提供了以下优势:
- 自动续期:通过看门狗机制避免业务执行时间过长导致的锁自动释放
- 可重入实现:支持同一线程多次获取同一把锁
- 阻塞等待:提供tryLock等方法支持超时等待
- Lua脚本:保证原子性操作
二、Redisson分布式锁的使用
2.1 基本使用示例
java
@Service
public class DistributedLockService {
@Autowired
private RedissonClient redissonClient;
public void processWithLock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,等待时间10秒,锁自动释放时间30秒
boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
System.out.println("获取锁成功,开始处理业务逻辑");
// 执行业务逻辑
doBusinessLogic();
System.out.println("业务逻辑处理完成");
} else {
System.out.println("获取锁失败");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("获取锁被中断");
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("锁已释放");
}
}
}
private void doBusinessLogic() {
try {
// 模拟业务处理时间
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
2.2 可重入锁示例
java
public class ReentrantLockDemo {
private final RedissonClient redissonClient;
private final RLock lock;
public ReentrantLockDemo(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
this.lock = redissonClient.getLock("reentrant:lock");
}
public void method1() {
lock.lock();
try {
System.out.println("执行方法1");
method2(); // 可重入调用
} finally {
lock.unlock();
}
}
public void method2() {
lock.lock(); // 同一线程再次获取锁
try {
System.out.println("执行方法2");
method3();
} finally {
lock.unlock();
}
}
public void method3() {
lock.lock(); // 同一线程第三次获取锁
try {
System.out.println("执行方法3");
} finally {
lock.unlock();
}
}
}
2.3 看门狗机制的自动续期
java
public class WatchDogDemo {
private final RedissonClient redissonClient;
public WatchDogDemo(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
public void longRunningTask() {
RLock lock = redissonClient.getLock("watchdog:lock");
try {
// 不指定锁的过期时间,启用看门狗机制
lock.lock();
System.out.println("开始执行长时间任务");
// 模拟长时间运行的任务(超过默认锁过期时间30秒)
for (int i = 0; i < 10; i++) {
Thread.sleep(10000); // 每次休眠10秒
System.out.println("任务进行中... " + (i + 1) * 10 + "秒");
}
System.out.println("长时间任务执行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
三、底层原理深度分析
3.1 加锁原理
Redisson使用Lua脚本来保证加锁操作的原子性,核心脚本如下:
lua
-- 加锁脚本
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]);
这个脚本的执行逻辑:
- KEYS[1]:锁的名称
- ARGV[1]:锁的过期时间(毫秒)
- ARGV[2]:线程标识(UUID + 线程ID)
执行流程:
- 如果锁不存在,创建锁并设置过期时间
- 如果锁存在且是当前线程持有,重入计数加1
- 否则返回锁的剩余存活时间
3.2 解锁原理
解锁同样使用Lua脚本保证原子性:
lua
-- 解锁脚本
if (redis.call('exists', KEYS[1]) == 0) then
return 1;
end;
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
return nil;
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then
redis.call('pexpire', KEYS[1], ARGV[2]);
return 0;
else
redis.call('del', KEYS[1]);
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
解锁逻辑:
- 锁不存在直接返回
- 当前线程不持有锁返回null
- 重入计数减1,如果大于0重新设置过期时间
- 如果计数为0,删除锁并发布解锁消息
3.3 看门狗(WatchDog)机制
看门狗机制是Redisson的核心特性,解决了业务执行时间不确定导致的锁提前释放问题。
3.3.1 看门狗的工作原理
实现原理概览
看门狗机制本质上是一个 后台定时任务,它:
- 在成功获取锁后自动启动
- 每隔一段时间检查当前线程是否仍然持有锁
- 如果是,则向 Redis 发送命令延长锁的过期时间(TTL)
- 直到锁被显式释放(unlock())或客户端断开
3.3.2 续期脚本
lua
-- 续期脚本
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('pexpire', KEYS[1], ARGV[1]);
return 1;
end;
return 0;
看门狗机制的关键点:
- 续期时机 :每隔
lockLeaseTime/3
时间执行一次续期 - 续期条件:只有当前线程持有锁时才能续期成功
- 自动停止:锁释放时自动停止看门狗任务
- 默认时间:默认锁存活时间为30秒,续期间隔为10秒
3.4 可重入实现原理
Redisson使用Hash数据结构实现可重入:
锁的数据结构:
KEY: lock:mylock
VALUE: {
"8743c9c0-0795-4907-87fd-6c719a6b4586:1": 3
}
- KEY:锁的名称
- Hash Field:客户端ID + 线程ID
- Hash Value:重入次数
这种设计的优势:
- 通过Hash结构存储线程标识和重入次数
- 原子性操作保证数据一致性
- 支持多线程并发访问不同的锁
四、总结
Redisson分布式锁通过以下几个关键机制实现了高可用、高性能的分布式锁方案:
- Lua脚本保证原子性:加锁、解锁、续期操作都通过Lua脚本实现原子性
- 看门狗机制解决续期问题:自动续期避免业务执行时间过长导致的锁丢失
- Hash结构实现可重入:使用Redis Hash存储线程信息和重入次数
- 发布订阅优化性能:通过Redis的pub/sub机制减少客户端轮询