Redisson分布式锁:看门狗机制与续期原理

文章目录

  • 前言
  • 一、分布式锁的基本概念
    • [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]);

这个脚本的执行逻辑:

  1. KEYS[1]:锁的名称
  2. ARGV[1]:锁的过期时间(毫秒)
  3. 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 看门狗的工作原理

实现原理概览

看门狗机制本质上是一个 后台定时任务,它:

  1. 在成功获取锁后自动启动
  2. 每隔一段时间检查当前线程是否仍然持有锁
  3. 如果是,则向 Redis 发送命令延长锁的过期时间(TTL)
  4. 直到锁被显式释放(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;

看门狗机制的关键点:

  1. 续期时机 :每隔 lockLeaseTime/3 时间执行一次续期
  2. 续期条件:只有当前线程持有锁时才能续期成功
  3. 自动停止:锁释放时自动停止看门狗任务
  4. 默认时间:默认锁存活时间为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分布式锁通过以下几个关键机制实现了高可用、高性能的分布式锁方案:

  1. Lua脚本保证原子性:加锁、解锁、续期操作都通过Lua脚本实现原子性
  2. 看门狗机制解决续期问题:自动续期避免业务执行时间过长导致的锁丢失
  3. Hash结构实现可重入:使用Redis Hash存储线程信息和重入次数
  4. 发布订阅优化性能:通过Redis的pub/sub机制减少客户端轮询
相关推荐
Micrle_0073 小时前
java分布式场景怎么实现一个高效的 读-写锁
java·分布式
2302_809798323 小时前
【Redis】缓存的穿透、击穿和雪崩
数据库·redis·缓存
楠枬3 小时前
Curator 如何实现分布式锁
分布式·zookeeper
Badman3 小时前
分布式系统下的数据一致性-Redis分布式锁
redis·分布式·后端
武子康6 小时前
Java-118 深入浅出 MySQL ShardingSphere 分片剖析:SQL 支持范围、限制与优化实践
java·大数据·数据库·分布式·sql·mysql·性能优化
努力努力再努力wz7 小时前
【c++进阶系列】:万字详解AVL树(附源码实现)
java·运维·开发语言·c++·redis
毕设源码-赖学姐7 小时前
【开题答辩全过程】以 基于Hadoop电商数据的可视化分析为例,包含答辩的问题和答案
大数据·hadoop·分布式
喂完待续9 小时前
【Big Data】Apache Kafka 分布式流处理平台的实时处理实践与洞察
分布式·kafka·消息队列·big data·数据处理·序列晋升
ACRELKY17 小时前
光伏运维迎来云端革命!AcrelCloud-1200如何破解分布式光伏四大痛点?
运维·分布式