【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 释放锁的是时候释放。

具体取消到期更新方法:

相关推荐
科技小花2 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸2 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain2 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希2 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神2 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员3 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java3 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿3 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴3 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU3 小时前
三大范式和E-R图
数据库