【分布式锁通关指南 10】源码剖析redisson之MultiLock的实现

news2025/5/17 13:17:25

引言

本期我们将把目光聚焦在 Redisson 中另一个颇具代表性的分布式锁实现——MultiLock。它的核心思想是:一次性对多个独立的 RLock 进行加锁或解锁操作,只有当多个锁都成功加锁时才算真正完成锁的获取,一旦有任何一个失败,整体操作都会回滚。这种“整锁整放”的方式,能更好地满足某些高要求的分布式业务场景。

介绍

在分布式环境中,如果我们将数据拆分到不同的 Redis 实例、集群或是不同的 key 上,有时会遇到需要“一次性对 N 个资源都上锁,才算占用资源”的场景。使用 Redisson 的 MultiLock 可以极大地简化这类需求的实现。它提供了一个整合多个 RLock 的抽象,对外暴露成单一的锁接口,使用起来就像在操作一把锁,而内部却是对多把锁的组合操作。

它的典型应用场景包括:

  • 同时锁定多个不同 Redis key,保证“要么全加锁成功,要么全部不加锁”。
  • 跨多个机房 / Redis 节点时的互斥需求,尤其在做异步、分布式任务调度时,减少了操作多个锁的繁琐。

接下来让我们直击源码,看一下 RedissonMultiLock(简称 MultiLock)是如何实现这一套逻辑的。

加锁

在 Redisson 源码里,MultiLock 的主要实现类是 org.redisson.RedissonMultiLock。其核心属性是一个 List<RLock>,用来保存所有需要一起加锁的锁。它本质上也是一个 java.util.concurrent.locks.Lock,所以有类似的 lock()tryLock() 等方法。

下面截取关键的加锁逻辑(为方便说明,做了适当精简):

public class RedissonMultiLock implements Lock {

    private final List<RLock> locks = new ArrayList<>();

    public RedissonMultiLock(RLock... locks) {
        this.locks.addAll(Arrays.asList(locks));
    }

    @Override
    public void lock() {
        lock(-1, null);
    }

    @Override
    public void lock(long leaseTime, TimeUnit unit) {
        boolean locked = tryLock(leaseTime, unit);
        if (!locked) {
            throw new IllegalStateException("Unable to acquire lock");
        }
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        long startTime = System.currentTimeMillis();
        List<RLock> acquiredLocks = new ArrayList<>();
        for (RLock lock : locks) {
            // 计算剩余的等待时间
            long elapsed = System.currentTimeMillis() - startTime;
            long remain = time - elapsed;
            if (remain <= 0 && time != -1) {
                // 超时了,回滚所有已获取的锁
                unlockInner(acquiredLocks);
                return false;
            }

            // 尝试获取锁
            boolean success;
            if (time == -1) {
                success = lock.tryLock(); // 不限等待时间
            } else {
                success = lock.tryLock(remain, unit);
            }

            if (!success) {
                // 获取锁失败,回滚
                unlockInner(acquiredLocks);
                return false;
            }
            acquiredLocks.add(lock);
        }
        return true;
    }

    private void unlockInner(List<RLock> locks) {
        for (RLock lock : locks) {
            try {
                lock.unlock();
            } catch (Exception e) {
                // 异常处理,通常记录日志或忽略
            }
        }
    }

    // ...
}

从这段代码中,我们可以看到:

  1. MultiLock 会将“要一起加锁”的多个 RLock 封装进一个 List
  2. 当调用 tryLock 时,会挨个尝试获取每个 RLock
  3. 若所有锁都获取成功,才返回 true
  4. 如果中途有任何一个锁获取失败或者超时,就会调用 unlockInner 方法,依次释放已成功获取的锁,回滚到初始状态,保持分布式环境下的原子性。

这样就保障了“要么所有锁都成功加锁,要么一个都不会留存”,消除了状态不一致的风险。

释放锁

MultiLock 的释放逻辑同样是“全部释放”或“都不释放”。来看下相关核心方法 unlock() 的实现(截取简化版本):

public void unlock() {
    // 这里我们是一次性 unlock 所有 locks
    for (RLock lock : locks) {
        try {
            lock.unlock();
        } catch (Exception e) {
            // 可能出现解锁异常,比如锁不属于当前线程等情况,做必要处理
        }
    }
}

可以看到,unlock() 内部直接遍历所有 RLock 并执行解锁操作。这意味着无论中途某个锁因为“非本线程占用”等原因导致报错,其余锁也会继续解锁,力求释放尽可能多的锁,尽量避免“部分锁未释放”造成死锁风险。

以此也体现了 MultiLock 的一贯思路:要么全部锁住,要么全部释放,让多个分布式锁在逻辑上“捆绑”成一体。

如何保证“一致性”?

  1. 获取失败即回滚
    当调用 tryLock 时,如果期间任何一个分布式锁无法加锁成功,就立即回滚(释放已获取的锁)。这是确保多锁原子性的关键。
  2. 重复可重入语义仍需依赖具体 RLock
    如果多个 RLock 中有些是可重入锁,那么在同一线程下反复获取时,并不会阻塞。MultiLock 并不会额外重写可重入逻辑,它更多地是一个“协调器”,背后依然由各个 RLock 自身的 reentrant 实现来支撑。
  3. 统一的超时控制
    tryLock(long time, TimeUnit unit) 会逐一减少剩余可用时间,避免因为某个锁获取太慢导致整个流程卡死。
  4. 释放过程对每个锁都负责
    哪怕出现解锁异常,MultiLock 也会继续释放其他锁,将风险与影响降至最低。

小结

RedissonMultiLock 通过将多把 RLock 打包成一个“组合锁”,让使用者在编程时只需关心“我拿到所有锁了吗?所有锁都释放了吗?”。它背后通过遍历加锁并回滚的策略,保证了原子性,避免了分布式环境下常见的“锁定不一致”问题。

与之前的公平锁等其他锁实现相比,MultiLock 并不是通过 Lua 脚本在单个 Redis 实例上实现的,而是通过对多个锁对象的封装来保证“一起成功或一起失败”。它更多用于满足“一次性锁定多资源”的场景,这比单一锁更适用分布式业务中对一致性、原子性要求更高的场景。

希望本文能帮助大家厘清 MultiLock 的实现原理。与其余 Redisson 锁一样,阅读源码的过程能让我们更好地理解其在分布式场景下如何保证安全与高效,也能启发我们在设计自定义分布式组件时,如何通过“组合”思维来化繁为简。我们下一期再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DBF Converter:高效转换DBF文件,满足多样化数据处理需求

DBF Converter 是一款功能强大的数据转换工具&#xff0c;专为需要将DBF文件转换为其他格式的用户设计。它支持将DBF文件转换为CSV、Excel、HTML、SQL等多种常见格式&#xff0c;满足用户在不同场景下的数据处理需求。无论是数据迁移、报表生成还是日常数据处理&#xff0c;DBF…

Jmeter 安装包与界面汉化

Jmeter 安装包&#xff1a; 通过网盘分享的文件&#xff1a;CSDN-apache-jmeter-5.5 链接: https://pan.baidu.com/s/17gK98NxS19oKmkdRhGepBA?pwd1234 提取码: 1234 Jmeter界面汉化&#xff1a;

【C++】 —— 笔试刷题day_29

一、排序子序列 题目解析 一个数组的连续子序列&#xff0c;如果这个子序列是非递增或者非递减的&#xff1b;这个连续的子序列就是排序子序列。 现在给定一个数组&#xff0c;然后然我们判断这个子序列可以划分成多少个排序子序列。 例如&#xff1a;1 2 3 2 2 1 可以划分成 …

MongoTemplate 基础使用帮助手册

前言 MongoDB 是一种流行的 NoSQL 数据库&#xff0c;适合存储大量的非结构化数据。MongoTemplate 是 Spring Data MongoDB 中的一个核心组件&#xff0c;它提供了一组丰富的 API 来与 MongoDB 进行交互。它封装了许多常见的数据库操作&#xff0c;使开发者能够轻松执行 CRUD 操…

图像处理:预览并绘制图像细节

前言 因为最近在搞毕业论文的事情&#xff0c;要做出一下图像细节对比图&#xff0c;所以我这里写了两个脚本&#xff0c;一个用于框选并同时预览图像放大细节&#xff0c;可显示并返回框选图像的坐标&#xff0c;另外一个是输入框选图像的坐标并将放大的细节放置在图像中&…

力扣热题——最长相邻不相等子序列 |

题目要求从字符串数组 words 中选出一个最长的子序列&#xff0c;使得该子序列中相邻字符串对应的 groups 数组中的值不同。通过贪心算法&#xff0c;可以高效地解决该问题。具体步骤为&#xff1a;初始化一个结果列表&#xff0c;遍历 words 数组&#xff0c;检查当前字符串的…

ssti刷刷刷

[NewStarCTF 公开赛赛道]BabySSTI_One 测试发现过滤关键字&#xff0c;但是特殊符号中括号、双引号、点都能用 可以考虑拼接或者编码&#xff0c;这里使用拼接 ?name{{()["__cla"~"ss__"]}}?name{{()["__cla"~"ss__"]["__ba&…

java+selenum专题(一)

环境搭建部署篇-> 1.简介 java版的selenium&#xff0c;介绍一下java selenium自动化测试。大致和pythonselenium自动化测试差不多。基于java和selenium做自动化测试&#xff0c;因此你必须会搭建基本的开发环境&#xff0c;掌握python基本的语法和一个IDE来进行开发&…

[逆向工程]DebugView捕获WPS日志?解析未运行WPS时Shell扩展加载的原因与解决方案(二十五)

[逆向工程]DebugView捕获WPS日志&#xff1f;解析未运行WPS时Shell扩展加载的原因与解决方案&#xff08;二十五&#xff09; 引言&#xff1a;一个“幽灵”般的日志问题 你是否在使用 DebugView 排查系统问题时&#xff0c;发现日志中频繁出现 WPS 相关模块&#xff08;如 k…

ACM模式用Scanner和System.out超时的解决方案和原理

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;笔试强训 &#x1f4da;本系列文章为个人学…

Java注解详解:从入门到实战应用篇

1. 引言 Java注解&#xff08;Annotation&#xff09;是JDK 5.0引入的一种元数据机制&#xff0c;用于为代码提供附加信息。它广泛应用于框架开发、代码生成、编译检查等领域。本文将从基础到实战&#xff0c;全面解析Java注解的核心概念和使用场景。 2. 注解基础概念 2.1 什…

QML 属性动画、行为动画与预定义动画

目录 引言相关阅读本文使用的动画属性工程结构示例解析示例1&#xff1a;属性动画应用示例2&#xff1a;行为动画实现示例3&#xff1a;预定义动画 总结工程下载 引言 QML动画系统为界面元素提供了流畅的过渡效果。本文通过三个示例&#xff0c;结合属性动画(PropertyAnimatio…

window nvidia-smi命令 Failed to initialize NVML: Unknown Error

如果驱动目录下的可以执行&#xff0c;那可能版本原因 "C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi"复制"C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe"替换 C:\Windows\System32\nvidia-smi.exe 或者 把C:\Windows\System3…

自学嵌入式 day19-数据结构 链表

二、线性表的链式存储 1.特点&#xff1a; &#xff08;1&#xff09;线性表链式存储结构的特点是一组任意的存储单位存储线性表的数据元素&#xff0c;存储单元可以是连续的&#xff0c;也可以不连续。可以被存储在任意内存未被占用的位置上。 &#xff08;2&#xff09;所以…

东芝第3代SiC MOSFET助于降低应用中电源损耗

功率器件是管理和降低各种电子设备电能功耗以及实现碳中和社会的重要元器件。由于与比硅材料相比&#xff0c;碳化硅具有更高的电压和更低的损耗&#xff0c;因此碳化硅&#xff08;SiC&#xff09;被广泛视为下一代功率器件的材料。虽然碳化硅功率器件目前主要用于列车逆变器&…

PD 分离推理的加速大招,百度智能云网络基础设施和通信组件的优化实践

为了适应 PD 分离式推理部署架构&#xff0c;百度智能云从物理网络层面的「4us 端到端低时延」HPN 集群建设&#xff0c;到网络流量层面的设备配置和管理&#xff0c;再到通信组件和算子层面的优化&#xff0c;显著提升了上层推理服务的整体性能。 百度智能云在大规模 PD 分离…

官方 Elasticsearch SQL NLPChina Elasticsearch SQL

官方的可以在kibana 控制台上进行查询&#xff1a; POST /_sql { “query”: “SELECT client_ip, status FROM logs-2024-05 WHERE status 500” } NLPChina Elasticsearch SQL就无法以在kibana 控制台上进行查询&#xff0c;但是可以使用postman接口进行查询&#xff1a;

5月16日复盘-目标检测开端

5月16日复盘 一、图像处理之目标检测 1. 目标检测认知 ​ Object Detection&#xff0c;是指在给定的图像或视频中检测出目标物体在图像中的位置和大小,并进行分类或识别等相关任务。 ​ 目标检测将目标的分割和识别合二为一。 ​ What、Where 2. 使用场景 目标检测用于…

mathematics-2024《Graph Convolutional Network for Image Restoration: A Survey》

推荐深蓝学院的《深度神经网络加速&#xff1a;cuDNN 与 TensorRT》&#xff0c;课程面向就业&#xff0c;细致讲解CUDA运算的理论支撑与实践&#xff0c;学完可以系统化掌握CUDA基础编程知识以及TensorRT实战&#xff0c;并且能够利用GPU开发高性能、高并发的软件系统&#xf…

IDEA怎么汉化idea中文改回英文版

第一步:点击左上角的File&#xff0c;然后选择Setting 第二步&#xff1a;Setting页面选择 Appearance & Behavior&#xff0c;然后展开System Settings&#xff0c;然后选择 Language and Region&#xff0c;进行修改 我操作的是2024年的版本 File->Settings -> Ap…