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

具体取消到期更新方法:

相关推荐
jiayou642 小时前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
李广坤1 天前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
爱可生开源社区2 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
随逸1772 天前
《从零搭建NestJS项目》
数据库·typescript
加号33 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏3 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
李慕婉学姐3 天前
Springboot智慧社区系统设计与开发6n99s526(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
百锦再3 天前
Django实现接口token检测的实现方案
数据库·python·django·sqlite·flask·fastapi·pip
tryCbest3 天前
数据库SQL学习
数据库·sql
jnrjian3 天前
ORA-01017 查找机器名 用户名 以及library cache lock 参数含义
数据库·oracle