Spring Boot 基于Redisson实现注解式分布式锁
依赖版本
- JDK 17
- Spring Boot 3.2.0
- Redisson 3.25.0
源码地址:Gitee
导入依赖
<properties><redisson.version>3.25.0</redisson.version>
</properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>${redisson.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency>
</dependencies>
配置文件
# application.yml
server:port: 8080spring:# ======== Redis配置 ========redis:redisson:file: classpath:redisson.yaml
# redisson.yaml
# 编码。默认值: org.redisson.codec.JsonJacksonCodec
codec: !<org.redisson.codec.Kryo5Codec> {}
# 线程池数量。默认值: 当前处理核数量 * 2
threads: 16
# Netty线程池数量。默认值: 当前处理核数量 * 2
nettyThreads: 32
# 传输模式。默认值: NIO
transportMode: "NIO"
# 监控锁的看门狗超时,单位:毫秒。默认值: 30000
lockWatchdogTimeout: 30000
# 是否保持订阅发布顺序。默认值: true
keepPubSubOrder: true# Redisson 单实例配置
singleServerConfig:# 节点地址。格式:redis://host:portaddress: "redis://127.0.0.1:6379"# 密码。默认值: nullpassword: null# 数据库编号。默认值: 0database: 0# 客户端名称(在Redis节点里显示的客户端名称)。默认值: nullclientName: null# 连接超时,单位:毫秒。默认值: 10000connectTimeout: 10000# 命令等待超时,单位:毫秒。默认值: 3000timeout: 3000# 命令失败重试次数。默认值: 3retryAttempts: 3# 命令重试发送时间间隔,单位:毫秒。默认值: 1500retryInterval: 1500# 最小空闲连接数。默认值: 32connectionMinimumIdleSize: 24# 连接池大小。默认值: 64connectionPoolSize: 64# 单个连接最大订阅数量。默认值: 5subscriptionsPerConnection: 5# 发布和订阅连接的最小空闲连接数。默认值: 1subscriptionConnectionMinimumIdleSize: 1# 发布和订阅连接池大小。默认值: 50subscriptionConnectionPoolSize: 50# DNS监测时间间隔,单位:毫秒。默认值: 5000dnsMonitoringInterval: 5000# 连接空闲超时,单位:毫秒。默认值: 10000idleConnectionTimeout: 10000
Redisson 锁简单使用
public void lock(String key) throws InterruptedException {RLock lock = redissonClient.getLock(key);log.info("[Redisson 分布式] 获取锁 KEY :{}", key);boolean lockSuccess = lock.tryLock(500, TimeUnit.MILLISECONDS);if (!lockSuccess) {throw new RuntimeException("获取锁失败");}try {//执行锁内的代码逻辑} finally {if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();log.info("[Redisson 分布式锁] 释放锁 KEY :{}", key);}}
}
Redisson锁的使用很方便,提供了很多的便携方法。但是在每个需要使用锁的地方都去写这样的模板代码有点“麻烦”,所以对Redisson锁的使用进行一个简单的封装,让在开发中使用更顺手
Redisson 锁工具类封装
RedisLockService 锁工具类
采用函数式接口,可在使用时对业务代码精准落锁,减少被锁的时间,提升系统性能。
package com.yiyan.study.utils.redis;import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;/*** 基于Redis Redisson 分布式锁工具类* * @createDate 2022-12-21*/
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class RedisLockService {private final RedissonClient redissonClient;// 编程式Redisson锁public <T> T executeWithLockThrows(String key, int waitTime, TimeUnit unit, SupplierThrow<T> supplier) throws Throwable {RLock lock = redissonClient.getLock(key);log.info("[Redisson 分布式] 获取锁 KEY :{}", key);boolean lockSuccess = lock.tryLock(waitTime, unit);if (!lockSuccess) {throw new RuntimeException("获取锁失败");}try {return supplier.execute();//执行锁内的代码逻辑} finally {if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();log.info("[Redisson 分布式锁] 释放锁 KEY :{}", key);}}}@SneakyThrowspublic <T> T executeWithLock(String key, int waitTime, TimeUnit unit, Supplier<T> supplier) {return executeWithLockThrows(key, waitTime, unit, supplier::get);}public <T> T executeWithLock(String key, Supplier<T> supplier) {return executeWithLock(key, -1, TimeUnit.MILLISECONDS, supplier);}/*** 函数式接口,用于执行锁内的代码逻辑*/@FunctionalInterfacepublic interface SupplierThrow<T> {T execute() throws Throwable;}
}
使用示例
public void lockLine() {// 模拟查询等不需要锁的操作ThreadUtil.sleep(2000L);redisLockService.executeWithLock("lockLine", 500, TimeUnit.MILLISECONDS,() -> {// 模拟上锁的数据操作ThreadUtil.sleep(200L);return null;});
}
Redisson 锁注解
在开发中,有些方法的功能就是原子性的,比如订单状态更新这样方法,此时方法内的代码都需要被锁住,所以可以采用注解的方式,来对需要加锁的业务进行上锁,避免编写重复冗余的代码。
RedissonLock注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;/*** Redisson 分布式锁注解* * @createDate 2023-09-18 07:17*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedissonLock {/*** key的前缀,默认取方法全限定名,除非我们在不同方法上对同一个资源做分布式锁,就自己指定** @return key的前缀*/String prefixKey() default "";/*** springEl 表达式** @return 表达式*/String key() default "";/*** 等待锁的时间,默认-1,不等待直接失败,redisson默认也是-1** @return 单位秒*/int waitTime() default -1;/*** 等待锁的时间单位,默认毫秒** @return 单位*/TimeUnit unit() default TimeUnit.MILLISECONDS;
}
注解切面
import com.yiyan.study.utils.SpElUtils;
import com.yiyan.study.utils.redis.RedisLockService;
import com.yiyan.study.utils.redis.annotation.RedissonLock;
import jakarta.annotation.Resource;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;/*** Redisson 分布式锁切面** @createDate 2023-09-18 07:17*/
@Slf4j
@Aspect
@Component
// 确保比事务注解先执行,分布式锁在事务外
@Order(0)
public class RedissonLockAspect {@Resourceprivate RedisLockService redisLockService;@Pointcut("@annotation(com.yiyan.study.utils.redis.annotation.RedissonLock)")public void lockPointcut() {}@Around("lockPointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();RedissonLock redissonLock = method.getAnnotation(RedissonLock.class);// 默认方法限定名+注解排名(可能多个)String prefix = StringUtil.isBlank(redissonLock.prefixKey()) ? SpElUtils.getMethodKey(method) : redissonLock.prefixKey();String key = prefix + ":" + SpElUtils.parseSpEl(method, joinPoint.getArgs(), redissonLock.key());int waitTime = redissonLock.waitTime();TimeUnit timeUnit = redissonLock.unit();return redisLockService.executeWithLockThrows(key, waitTime, timeUnit, joinPoint::proceed);}
}
使用示例
@RedissonLock(prefixKey = "lockFunc", waitTime = 500)
public void lockFunc() {// 模拟查询等不需要锁的操作ThreadUtil.sleep(2000L);// 模拟上锁的数据操作ThreadUtil.sleep(200L);
}
Redisson 锁测试
编写测试接口
import com.yiyan.study.utils.redis.RedisLockService;
import com.yiyan.study.utils.redis.annotation.RedissonLock;
import jakarta.annotation.Resource;
import jodd.util.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/")
@Slf4j
public class RedisLockController {@Resourceprivate RedisLockService redisLockService;@GetMapping("/lock_func")@RedissonLock(prefixKey = "lockFunc", waitTime = 500)public void lockFunc() {// 模拟查询等不需要锁的操作ThreadUtil.sleep(2000L);// 模拟上锁的数据操作ThreadUtil.sleep(200L);}@GetMapping("/lock_line")public void lockLine() {// 模拟查询等不需要锁的操作ThreadUtil.sleep(2000L);redisLockService.executeWithLock("lockLine", 500, TimeUnit.MILLISECONDS,() -> {// 模拟上锁的数据操作ThreadUtil.sleep(200L);return null;});}
}
测试
使用AB测试工具,10个请求,并发为5,模拟总业务时常2.5s:
相关文章:

Spring Boot 基于Redisson实现注解式分布式锁
依赖版本 JDK 17 Spring Boot 3.2.0 Redisson 3.25.0 源码地址:Gitee 导入依赖 <properties><redisson.version>3.25.0</redisson.version> </properties><dependencies><dependency><groupId>org.projectlombok</…...
Javascript 正则表达式零宽断言
在介绍正则表达式零宽断言这个概念之前,先看一下以下这道有关 javascript 正则表达式的题目: 登录注册流程是前端最常见的业务流程之一,注册流程少不了密码强弱度校验,请实现对密码的校验,要求满足: 包含大…...
Chocolatey
Chocolatey Software | PHP (Hypertext Preprocessor) 8.3.1 msi安装包https://github.com/chocolatey/choco/releases/download/2.2.2/chocolatey-2.2.2.0.msi 设置/安装 巧克力味Chocolatey CLI (choco)设置/安装 要求 受支持的 Windows 版本Windows …...

雍禾植发成毛发行业标杆!雍禾医疗获“年度医疗大健康消费企业”
近期,以“新视野 新链接”为主题的2023 EDGE AWARDS全球创新评选榜单正式发布。该评选由钛媒体发起,聚焦大健康产业,由权威行业专家、王牌分析师、专业投资机构、用户代表共同评审,兼顾综合专业性、影响力、创新性三大维度评选而出…...
Linux内核--进程管理(十二)共享内存和信号量
目录 一、引言 二、基础知识 三、统一封装的接口 ------>3.1、kern_ipc_perm 四、共享内存的创建和映射 ------>4.1、创建共享内存 ------>4.2、共享内存的映射 五、信号量的创建和使用 ------>5.1、信号量的创建 ------>5.2、信号量的初始化 ------…...
java 构造方法
构造方法 1、什么是构造方法,有什么用? 构造方法是一个比较特殊的方法,通过构造方法可以完成对象的创建,以及实例变量的初始化。 换句话说:构造方法是用来创建对象,并且同时给对象的属性赋值。 注意&#x…...
CISSP 第2章: 人员安全和风险管理概念
第二章 人员安全和风险管理概念 2.1 促进人员安全策略 构建工作描述方面的重要因素包括: 职责分离: 把关键的、重要的和敏感工作任务分配给若干不同的管理员或高级执行者,防止共谋 工作职责:最小特权原则 岗位轮换:提供知识冗余,减少伪造、数据更改、偷…...
前端八股文(CSS篇)一
目录 1.px和em的区别 2.介绍下BFC及其应用 3.介绍下粘性布局(sticky) 4.清除浮动的方法 5.如何用css或js实现多行文本溢出省略效果,考虑兼容 6.如何触发重排和重绘? 7.重绘与重排的区别? 8.说说两种盒模型以及区…...
游戏加速器LSP/DLL导致WSL.EXE无法打开问题修复!
解决办法: https://github.com/microsoft/WSL/issues/4177#issuecomment-597736482 方法一:(管理员身份) netsh winsock reset 方法二: WSCSetApplicationCategory 函数设置LSP加载权限 bool NoLsp(const wchar_t* …...

宏电股份5G RedCap终端产品助力深圳极速先锋城市建设
12月26日,“全城全网,先锋物联”深圳移动5G-A RedCap助力深圳极速先锋城市创新发布会举行,宏电股份携一系列5G RedCap终端产品应邀参与创新发布会,来自全国5G生态圈的各界嘉宾、专家学者济济一堂,共探信息化数字化创新…...
linux top命令中 cpu 利用率/mem 使用率与load average平均负载计算方式
文章目录 1 简介2 CPU% 字段3 MEM% 字段4 load average 平均负载 1 简介 top 命令是 Linux 上一个常用的系统监控工具,它经常用来监控 Linux 的系统状态,是常用的性能分析工具,能够显示较全的系统资源信息,包括系统负载ÿ…...

win11出现安全中心空白和IT管理员已限制对某些区域的访问(不一样的解决方式),真实的个人经历,并且解决经过
1、个人的产生问题的经历 2023年12月22日,由于我买了一块电脑的固态硬盘1T,想要扩容,原来电脑自带512G(由于个人是一个程序员,导致512G实在太古鸡肋)装好以后,想要重装一下系统,来个大清理。结果不出意料&…...
关于安卓重启设备和重启应用进程
android 重启应用进程 //多种方式重启应用进程public class MainActivity {//重启当前Applicationprivate void restartApplication(){final Intent intent getPackageManager().getLaunchIntentForPackage(getPackageName());intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP…...

Linux内核--进程管理(十三)O(1)调度算法
目录 一、引言 二、O(1)调度算法原理 ------>2.1、prio_array 结构 ------>2.2、runqueue 结构 三、实时进程调度 四、普通进程调度 ------>4.1、运行时间片计算 五、O(1)调度算法实现 ------>5.1、时钟中断任务调度 ------>5.2、任务调度 一、引言 …...
【QT】发生的运行时错误汇总
1 、QObject::startTimer: Timers cannot be started from another thread 错误原因:QObject是可重入的,它的大多数非GUI子类,例如QTimer, QTcpSocket, QUdpSocket and QProcess都是可重入的,使得这些类可以同时用于多线程。需要…...

机器学习常用算法模型总结
文章目录 1.基础篇:了解机器学习1.1 什么是机器学习1.2 机器学习的场景1.2.1 模式识别1.2.2 数据挖掘1.2.3 统计学习1.2.4 自然语言处理1.2.5 计算机视觉1.2.6 语音识别 1.3 机器学习与深度学习1.4 机器学习和人工智能1.5 机器学习的数学基础特征值和特征向量的定义…...

笔记中所得(已删减)
1.交流电的一个周期内电压/电流的平均值都为0 2.电动势:电池将单位正电荷由负极搬到正极所做的功 5.额定能量:电池的额定容量乘以标称电压,以Wh为单位 6.500mAh意义是可以以500mA的电流放电1小时 7.电池容量的单位是mAh 13.实际电流源不能串联 14. 15. 16. 17. 18. 19.电…...
在Django5中使用Websocket进行通信
Docker安装Redis docker run --restartalways -p 6379:6379 --name redis -d redis:7.0.12 --requirepass zhangdapeng520安装依赖 参考文档:https://channels.readthedocs.io/en/latest/installation.html pip install "channels[daphne]"展示聊天页…...

外汇天眼:CySEC与NAGA Markets Europe达成15万欧元的和解
塞浦路斯证券交易委员会(CySEC)已经与NAGA Markets Europe达成15万欧元的和解。有关监管决定的会议于2023年3月举行,然而直到今天才公布这个决定。 该和解符合2009年塞浦路斯证券交易委员会法第37(4)条的规定,该条赋予CySEC就任何…...
Docker仓库搭建与镜像推送拉取
1.Docker镜像仓库 搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现。 官网地址:https://hub.docker.com/_/registry 1.1.简化版镜像仓库 Docker官方的Docker Registry是一个基础版本的Docker镜像仓库,具备仓库管理的完整功能,…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...