【Redisson】锁的可重试原理和看门狗机制

目录

一、概要

1、核心源码

2、重试逻辑:

3、超时控制:

4、异步处理:

二、源码分析

1、获取锁的lua脚本

2、tryLock方法的可重试逻辑

3、小结:

三、看门狗机制

1、解决锁超时问题:

2、锁续期机制:

3、续约方法里面:

4、Task任务执行内容

5、锁释放


大家好,今天给大家分享一下redisson的锁是如何实现可重试的和锁续期的。

一、概要

1、核心源码

Redisson 的锁可重试机制主要通过tryAcquireAsync方法和定时任务实现。以下是核心源码的简化版本,展示了重试逻辑的实现原理:

java 复制代码
// RedissonLock.java 核心源码简化版
public class RedissonLock implements RLock {
    
    // 尝试获取锁,支持重试
    private <T> RFuture<T> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        // 转换等待时间
        long currentWaitTime = unit.toMillis(waitTime);
        
        // 首次尝试获取锁
        RFuture<Boolean> tryAcquireFuture = tryLockInnerAsync(
            leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
            
        // 处理获取结果
        return handleAcquireResult(tryAcquireFuture, currentWaitTime, leaseTime, unit, threadId);
    }
    
    // 处理获取锁的结果
    private <T> RFuture<T> handleAcquireResult(RFuture<Boolean> future, 
                                          long currentWaitTime, 
                                          long leaseTime, 
                                          TimeUnit unit, 
                                          long threadId) {
        // 如果获取成功,直接返回
        if (future.isSuccess() && future.getNow()) {
            return completedFuture(null);
        }
        
        // 如果等待时间已过,返回失败
        if (currentWaitTime <= 0) {
            return new FailedFuture<>(new LockException("Lock acquisition timeout"));
        }
        
        // 订阅锁释放事件,准备重试
        return subscribeAndRetry(currentWaitTime, leaseTime, unit, threadId);
    }
    
    // 订阅锁释放事件并安排重试
    private <T> RFuture<T> subscribeAndRetry(long currentWaitTime, 
                                         long leaseTime, 
                                         TimeUnit unit, 
                                         long threadId) {
        // 创建订阅,当锁释放时会收到通知
        RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
        
        // 订阅成功后安排重试
        return subscribeFuture.thenCompose(entry -> {
            // 计算剩余等待时间
            long elapsedTime = calculateElapsedTime(subscribeFuture);
            long currentRemainingTime = currentWaitTime - elapsedTime;
            
            // 如果时间已过,取消订阅并返回失败
            if (currentRemainingTime <= 0) {
                unsubscribe(entry, threadId);
                return new FailedFuture<>(new LockException("Lock acquisition timeout"));
            }
            
            // 创建延迟重试任务
            return scheduleRetryTask(currentRemainingTime, leaseTime, unit, threadId, entry);
        });
    }
    
    // 安排重试任务
    private <T> RFuture<T> scheduleRetryTask(long waitTime, 
                                        long leaseTime, 
                                        TimeUnit unit, 
                                        long threadId,
                                        RedissonLockEntry entry) {
        // 创建一个延迟任务,在收到锁释放通知后重试
        CompletableFuture<T> result = new CompletableFuture<>();
        
        // 注册锁释放监听器
        entry.addListener(new LockListener() {
            @Override
            public void onLockReleased() {
                // 锁释放后,立即尝试重新获取
                RFuture<Boolean> tryAcquireFuture = tryLockInnerAsync(
                    leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
                
                // 处理重试结果
                handleRetryResult(tryAcquireFuture, waitTime, leaseTime, unit, threadId, entry, result);
            }
        });
        
        return new CompletableFutureWrapper<>(result);
    }
    
    // 处理重试结果
    private void handleRetryResult(RFuture<Boolean> future, 
                               long waitTime, 
                               long leaseTime, 
                               TimeUnit unit, 
                               long threadId,
                               RedissonLockEntry entry,
                               CompletableFuture result) {
        future.whenComplete((acquired, e) -> {
            if (e != null) {
                unsubscribe(entry, threadId);
                result.completeExceptionally(e);
                return;
            }
            
            if (acquired) {
                unsubscribe(entry, threadId);
                result.complete(null);
            } else {
                // 未获取到锁,继续等待或超时
                long currentWaitTime = calculateRemainingTime(waitTime);
                if (currentWaitTime <= 0) {
                    unsubscribe(entry, threadId);
                    result.completeExceptionally(new LockException("Lock acquisition timeout"));
                }
                // 继续等待锁释放事件...
            }
        });
    }
    
    // 其他辅助方法...
}

2、重试逻辑

  • 通过tryAcquireAsync方法尝试获取锁,失败时进入重试流程
  • 使用subscribe方法订阅锁释放事件,当锁被释放时收到通知
  • 收到通知后立即重试获取锁,实现快速响应

3、超时控制

  • 每次重试前计算剩余等待时间currentWaitTime
  • 如果时间耗尽,取消订阅并返回超时异常
  • 通过calculateElapsedTimecalculateRemainingTime方法精确控制时间

4、异步处理

  • 所有操作都通过RFuture异步执行,不阻塞线程
  • 使用thenComposewhenComplete等方法组合异步操作
  • 通过CompletableFuture实现结果回调

二、源码分析

1、获取锁的lua脚本

2、tryLock方法的可重试逻辑

如果在time时间内获取到了锁释放的通知,继续往下执行:


3、小结:

lua脚本释放锁的时候,会发一个通知。外部获取锁失败时,会计算还有没有剩余等待时间,如果有,那么就订阅锁释放的通知,锁释放了之后才开始重试获取锁。如果还是获取失败,后面还是一样的通过一个while(true)循环,不断地检查是否有剩余时间、订阅锁释放通知、重试获取锁。直到获取到锁或者没有了剩余等待时间才结束。

三、看门狗机制

1、解决锁超时问题:

线程阻塞,锁超时释放后。别的线程获取到了锁。

2、锁续期机制:

3、续约方法里面:

4、Task任务执行内容

5、锁释放

那既然锁是递归地无限刷新锁的剩余时间的,那锁什么时候释放呢?

答案:当调用 unlock 释放锁的是时候释放。

具体取消到期更新方法:

相关推荐
熙客27 分钟前
Redis底层数据结构与内部实现
数据库·redis·mybatis
坤小满学Java1 小时前
【郑州轻工业大学|数据库】数据库课设-酒店管理系统
数据库·mysql·课程设计
DQI-king1 小时前
ZYNQ学习记录FPGA(五)高频信号中的亚稳态问题
数据库
Yrrr13 小时前
Redis 持久化机制详解:RDB、AOF 原理与面试最佳实践(AOF篇)
数据库·redis·面试·职场和发展
知月玄3 小时前
网页后端开发(基础5--JDBC VS Mybatis)
数据库·mysql
怪只怪满眼尽是人间烟火3 小时前
SQL分片工具类
网络·数据库·sql
国际云3 小时前
腾讯云搭建web服务器的方法
服务器·数据库·云计算·区块链
袋鼠云数栈3 小时前
AI Infra 运维实践:DeepSeek 部署运维中的软硬结合
大数据·运维·数据库·数据中台·数栈
苏格拉没有底_coder4 小时前
【Redis】Sentinel哨兵
redis·sentinel
云和恩墨5 小时前
国网某省电力借zDBM重构数据库容灾防线,400TB核心资产迈入分布式实时保护时代
数据库·分布式·重构