简单理解下基于 Redisson 库的分布式锁机制

目录

    • 简单理解下基于 Redisson 库的分布式锁机制
      • 代码流程:
        • 方法的调用:
        • 具体锁的实现:
          • riderBalance 方法:
          • tryLock 方法(重载):
          • tryLock 方法(核心实现):

在这里插入图片描述

简单理解下基于 Redisson 库的分布式锁机制

这段代码实现了一个基于 Redisson 库的分布式锁机制

Redisson 是一个基于 Redis 的 Java 客户端库,它提供了丰富的分布式数据结构和工具,旨在使 Java 开发者能够轻松地在分布式环境下使用 Redis。

Redisson 是 Redis 的高级封装,除了常规的 Redis 命令支持外,它还提供了很多额外的功能,如分布式锁、分布式集合、分布式队列、分布式缓存等,使得开发者在分布式系统中实现高效、可靠的操作。

比方对一个用户所拥有的金额进行增减操作,肯定需要上锁才能保证一定的安全性。

代码流程:

方法的调用:

一个简单的流程:

LockUtil.riderBalance(riderId, () -> {…}) 是一个加锁操作,是一种使用分布式锁来确保线程安全的机制,确保在修改 余额时,避免并发冲突。

简要来说,它的作用是在执行余额操作时,确保只有一个线程能够对指定的骑手账户进行操作,避免多个线程同时修改余额,导致数据不一致的情况。

LockUtil.riderBalance(riderId, …) 通过 riderId 获取一个锁,这样就能确保在同一时刻,只有一个线程可以执行传入的操作逻辑。

riderId 是骑手的唯一标识,通过它来加锁,防止对同一个骑手账户的并发操作。

() -> {…}: 这是一个 Lambda 表达式,表示当获得锁之后要执行的具体操作。这个操作包括:获取骑手的余额信息、执行余额扣除、记录操作日志等步骤。

    /**
     * 扣除余额
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deductionBalance(Integer riderId, BigDecimal amount, String title, Integer orderId, String orderCode, RiderCashlogType cashlogType, RiderCashlogStatus cashlogStatus) {
        return LockUtil.riderBalance(riderId, () -> {
            // 变更前
            RiderServiceDto riderBefore = this.getRiderById(riderId);

            // 减少余额 -------- ★★★★★★★★★★★★★★★★★★★★★★★★
            boolean flag = xxRiderService.deductionBalance(riderId, amount);

            // 变更后
            RiderServiceDto riderAfter = this.getRiderById(riderId);

            // 增加流水日志
            if (flag) {
                RiderCashlogServiceDto cashlog = new RiderCashlogServiceDto();
                cashlog.setMoneyType(RiderCashlogMoneyType.BALANCE);
                cashlog.setTitle(title);
                ......
				
                return xxRiderCashlogService.addCashlog(cashlog);
            }
            return false;
        }, () -> {
            throw BizException.newInstance(ErrorCode.BUSY);
        });
    }

调用接口方法

    /**
     * 扣除余额
     */
    boolean deductionBalance(Integer riderId, BigDecimal amount);

这里再进行详细的扣除方法,依然是用同个锁

    /**
     * 扣除余额
     */
    @Override
    public boolean deductionBalance(Integer riderId, BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) == 0) {
            return true;
        }
        // 加锁
        return LockUtil.riderBalance(riderId, () -> {
            if (ObjectUtil.isEmpty(riderId) || amount.compareTo(BigDecimal.ZERO) < 0) {
                log.error("扣除xx余额失败,riderId:{},amount:{}", riderId, amount);
                return false;
            }
            RiderServiceDto rider = this.getRiderById(riderId);
            // 获取当前账号的余额
            BigDecimal valid = rider.getValidMoney();
            // 判断当前余额是否足够
            if (valid.compareTo(amount) < 0) {
                throw BizException.newInstance(ErrorCode.ARGUMENT_ERROR, "扣除余额失败,余额小于" + amount.toPlainString());
            }

            return this.update(new UpdateWrapper<RiderEntity>().lambda()
                    .eq(RiderEntity::getId, riderId)
                    .setSql("valid_money = valid_money - " + amount));
        }, () -> {
            throw BizException.newInstance(ErrorCode.BUSY);
        });

    }

具体锁的实现:
riderBalance 方法:
    /**
     * 锁骑手余额------
     * Integer riderId: 骑手的 ID,作为分布式锁的标识。
     * Supplier<T> supplier: 提供数据或执行操作的函数接口。
     *                       传入的 Supplier<T> 函数接口,用来提供需要执行的业务操作。如果获取锁成功,业务操作会在锁内部执行。
     * InvokeInter lockFail: 如果获取锁失败,执行的操作。
     */
    public static <T> T riderBalance(Integer riderId, Supplier<T> supplier, InvokeInter lockFail) {
        return tryLock(RedisKey.LOCK_RIDER_BALANCE_XXXXX.getKey(riderId), supplier, lockFail);
    }
tryLock 方法(重载):
    /**
     * 设置分布式锁
     */
    public static <T> T tryLock(String key, Supplier<T> supplier, InvokeInter lockFail) {
    	//这是 tryLock 的一个重载方法,它会在尝试获取锁时默认设置超时时间为 10 秒
    	//通过 key、supplier 和 lockFail,它会调用 tryLock 的另一个重载版本,并使用 10 秒的默认超时时间
        return tryLock(key, supplier, lockFail, 10, TimeUnit.SECONDS);
    }
tryLock 方法(核心实现):

    /**
     * 设置分布式锁-----------
     * key: 锁的标识符,通常是与某个资源(如骑手余额)相关的唯一标识。
	 * supplier: 需要执行的业务逻辑。
	 * lockFail: 获取锁失败时的回调操作。
	 * amount 和 unit: 锁的持有时间(amount)和时间单位(unit),用于控制获取锁的最长等待时间。
     */
    public static <T> T tryLock(String key, Supplier<T> supplier, InvokeInter lockFail, long amount, TimeUnit unit) {
    	//通过 Redisson 客户端获取一个 RLock 对象,RLock 是 Redisson 提供的分布式锁实现。
        RLock lock = _this.redissonClient.getLock(key);
        try {
        	// lock.isLocked():判断锁是否已经被其他线程持有。
        	//lock.isHeldByCurrentThread():判断当前线程是否已经持有该锁。如果当前线程已经持有锁,它不需要重复获取。
            if (lock.isLocked() && !lock.isHeldByCurrentThread()) {
                if (lockFail == null) {
                	// 如果 lockFail 为空,则抛出 BizException 异常,表示当前资源忙(例如余额操作正在进行)
                    throw BizException.newInstance(ErrorCode.BUSY);
                }
                // 如果锁已经被其他线程持有且不是当前线程持有,且 lockFail 不为空,就执行 lockFail.invoke() 来处理锁获取失败的情况。
                lockFail.invoke();
                //lock.tryLock(amount, unit):尝试在指定的时间内(amount 秒)获取锁。如果成功,执行后续的业务操作。
            }  else if (lock.tryLock(amount, unit)) {
                try {
                	// 如果锁获取成功,调用 supplier.get() 执行传入的业务逻辑。
                    return supplier.get();
                } finally {
                	//无论业务逻辑执行成功与否,都要确保释放锁,lock.unlock() 用于释放锁,防止死锁。
                    lock.unlock();
                }
            } else {
            	//如果在 tryLock 的超时时间内无法获得锁(即返回 false),就执行 lockFail.invoke(),或者抛出 BizException 异常表示资源繁忙
                if (lockFail == null) {
                    throw BizException.newInstance(ErrorCode.BUSY);
                }
                lockFail.invoke();
            }
            //最终返回 null,因为所有锁的操作是围绕着锁获取和释放展开的,所有的业务执行通过 supplier.get() 来完成,返回值由 supplier 提供
            return null;
        } catch (InterruptedException e) {
        	//如果线程在等待锁的过程中被中断,则抛出 BizException 异常,表示当前资源忙(此时无法完成操作)。
            throw BizException.newInstance(ErrorCode.BUSY);
        }
    }

这段代码主要实现了一个分布式锁的功能,通过 Redisson 提供的 RLock 来确保在分布式环境下对资源的访问是互斥的,避免多个线程或进程同时访问同一资源(如xxx余额)。

tryLock 方法用于尝试获取锁,并在成功获取锁时执行指定的业务操作(supplier.get())。如果获取锁失败,依据 lockFail 参数的值执行相应的失败处理操作。

锁的获取有超时机制,超过超时时间仍无法获取锁时会触发失败处理

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小鹏汽车智慧材料数据库系统项目总成数据同步

1、定时任务处理 2、提供了接口 小鹏方面提供的推送的数据表结构&#xff1a; 这几个表总数为100多万&#xff0c;经过条件筛选过滤后大概2万多条数据 小鹏的人给的示例图&#xff1a; 界面&#xff1a; SQL: -- 查询车型 select bmm.md_material_id, bmm.material_num, bm…

LeetCode 3244.新增道路查询后的最短距离 II:贪心(跃迁合并)-9行py(O(n))

【LetMeFly】3244.新增道路查询后的最短距离 II&#xff1a;贪心&#xff08;跃迁合并&#xff09;-9行py&#xff08;O(n)&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-ii/ 给你一个整数 n 和一个二维…

华为无线AC+AP组网实际应用小结

之前公司都是使用的H3C的交换机、防火墙以及无线AC和AP的&#xff0c;最近优化下无线网络&#xff0c;说新的设备用华为的&#xff0c;然后我是直到要部署的当天才知道用华为设备的&#xff0c;就很无语了&#xff0c;一点准备没有&#xff0c;以下为这次的实际操作记录吧&…

Fakelocation Server服务器/专业版 Windows11

前言:需要Windows11系统 Fakelocation开源文件系统需求 Windows11 | Fakelocation | 任务一 打开 PowerShell&#xff08;以管理员身份&#xff09;命令安装 Chocolatey Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProto…

C语言基础学习:抽象数据类型(ADT)

基础概念 抽象数据类型&#xff08;ADT&#xff09;是一种数据类型&#xff0c;它定义了一组数据以及可以在这组数据上执行的操作&#xff0c;但隐藏了数据的具体存储方式和实现细节。在C语言中&#xff0c;抽象数据类型&#xff08;ADT&#xff09;是一种非常重要的概念&…

基于深度学习CNN算法的花卉分类识别系统01--带数据集-pyqt5UI界面-全套源码

文章目录 基于深度学习算法的花卉分类识别系统一、项目摘要二、项目运行效果三、项目文件介绍四、项目环境配置1、项目环境库2、环境配置视频教程 五、项目系统架构六、项目构建流程1、数据集2、算法网络Mobilenet3、网络模型训练4、训练好的模型预测5、UI界面设计-pyqt56、项目…

Bokeh实现大规模数据可视化的最佳实践

目录 引言 一、Bokeh简介 二、安装Bokeh 三、数据准备 四、性能优化 五、创建图表 六、添加交互功能 七、应用案例 八、高级技巧 九、总结 引言 在数据科学领域,数据可视化是一个至关重要的环节。通过可视化,我们可以直观地理解数据的特征和趋势,为数据分析和决策…

Easyexcel(4-模板文件)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09;Easyexcel&#xff08;4-模板文件&#xff09; 文件导出 获取 resources 目录下的文件&#xff0c;使用 withTemplate 获…

【山大909算法题】2014-T1

文章目录 1.原题2.算法思想3.关键代码4.完整代码5.运行结果 1.原题 为带表头的单链表类Chain编写一个成员函数Reverse&#xff0c;该函数对链表进行逆序操作&#xff08;将链表中的结点按与原序相反的顺序连接&#xff09;&#xff0c;要求逆序操作就地进行&#xff0c;不分配…

[Redis#2] 定义 | 使用场景 | 安装教程 | 快!

目录 1. 定义 In-memory data structures 在内存中存储数据 2. 优点&#xff01;快 Programmability 可编程性 Extensibility 扩展性 Persistence 持久化 Clustering 分布式集群 High availability 高可用性 ⭕快速访问的实现 3. 使用场景 1.Real-time data store …

学习编程,学习中间件,学习源码的思路

01 看的多&#xff0c;内化不足 最近想复习一下编程相关的知识&#xff0c;在复习前我翻开了之前的一些笔记&#xff0c;这些笔记基本都是从书本、视频、博客等摘取记录的&#xff0c;看着这些笔记心里总结&#xff1a;看的多&#xff0c;内化不足。 02 整理大纲 为了解决这个…

hhdb数据库介绍(10-2)

集群管理 计算节点集群 集群管理主要为用户提供对计算节点集群的部署、添加、启停监控、删除等管理操作。 集群管理记录 集群管理页面显示已部署或已添加的计算节点集群信息。可以通过左上角搜索框模糊搜索计算节点集群名称进行快速查找。同时也可以通过右侧展开展开/隐藏更…

AG32既可以做MCU,也可以仅当CPLD使用

Question: AHB总线上的所有外设都需要像ADC一样&#xff0c;通过cpld处理之后才能使用? Reply: 不用。 除了ADC外&#xff0c;其他都是 mcu可以直接配置使用的。 Question: DMA和CMP也不用? Reply: DMA不用。 ADC/DAC/CMP 用。 CMP 其实配置好后&#xff0c;可以直…

贪心算法(1)

目录 柠檬水找零 题解&#xff1a; 代码&#xff1a; 将数组和减半的最少操作次数&#xff08;大根堆&#xff09; 题解&#xff1a; 代码&#xff1a; 最大数&#xff08;注意 sort 中 cmp 的写法&#xff09; 题解&#xff1a; 代码&#xff1a; 摆动序列&#xff0…

网络爬虫——综合实战项目:多平台房源信息采集与分析系统

1. 项目背景与目标 1.1 项目背景 随着房产市场的快速发展&#xff0c;各大平台上充斥着大量房源信息。为了帮助用户快速掌握市场动态&#xff0c;需要通过爬虫技术自动采集多平台数据&#xff0c;清洗后进行存储和分析&#xff0c;为用户提供有价值的洞察。开发者通过这一实战…

数据结构-7.Java. 对象的比较

本篇博客给大家带来的是java对象的比较的知识点, 其中包括 用户自定义类型比较, PriorityQueue的比较方式, 三种比较方法...... 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 .…

NVR录像机汇聚管理EasyNVR多品牌NVR管理工具/设备如何使用Docker运行?

在当今的安防监控领域&#xff0c;随着视频监控技术的不断发展和应用范围的扩大&#xff0c;如何高效、稳定地管理并分发视频流资源成为了行业内外关注的焦点。EasyNVR作为一款功能强大的多品牌NVR管理工具/设备&#xff0c;凭借其灵活的部署方式和卓越的性能&#xff0c;正在引…

LSTM原理解读与实战

在RNN详解及其实战中&#xff0c;简单讨论了为什么需要RNN这类模型、RNN的具体思路、RNN的简单实现等问题。同时&#xff0c;在文章结尾部分我们提到了RNN存在的梯度消失问题&#xff0c;及之后的一个解决方案&#xff1a;LSTM。因此&#xff0c;本篇文章主要结构如下&#xff…

Springboot之登录模块探索(含Token,验证码,网络安全等知识)

简介 登录模块很简单&#xff0c;前端发送账号密码的表单&#xff0c;后端接收验证后即可~ 淦&#xff01;可是我想多了&#xff0c;于是有了以下几个问题&#xff08;里面还包含网络安全问题&#xff09;&#xff1a; 1.登录时的验证码 2.自动登录的实现 3.怎么维护前后端…

使用Element UI实现前端分页,前端搜索,及el-table表格跨页选择数据,切换分页保留分页数据,限制多选数量

文章目录 一、前端分页1、模板部分 (\<template>)2、数据部分 (data)3、计算属性 (computed)4、方法 (methods) 二、前端搜索1、模板部分 (\<template>)2、数据部分 (data)3、计算属性 (computed)4、方法 (methods) 三、跨页选择1、模板部分 (\<template>)2、…