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:

springboot3-redisson-lock-测试

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

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

相关文章

为什么网络安全行业那么缺人才,但招聘岗位却没那么多?

文章目录 一、学校的偏向于学术二、学的东西太基础三、不上班行不行 为什么网络安全的人才缺口那么大&#xff0c;但是大学毕业能找到网安工作的人却很少&#xff0c;就连招聘都没有其他岗位多&#xff1f; 明明央视都说了网络安全的人才缺口还有300多万&#xff0c;现在找不…

Python 下载与安装

1、下载 打开Python官网&#xff1a;Welcome to Python.org 点击下图所示的【Downloads】按钮进入下载页面。 ​ 进入下载页面后下拉至下图位置&#xff0c;选择版本&#xff0c;点击下载按钮下载。 页面会跳转至下一页下载页面&#xff0c;下拉到下图位置&#xff0c;选择…

18 UVM Scoreboard

UVM scoreboard是一个检查DUT功能的组件。它用analysis export从monitor接受transaction事务以进行检查。 uvm_scoreboard class declaration: virtual class uvm_scoreboard extends uvm_component User-defined scoreboard class declaration: 用户定义的scoreboard是从 u…

非对称加密与对称加密的区别是什么?

在数据通信中&#xff0c;加密技术是防止数据被未授权的人访问的关键措施之一。而对称加密和非对称加密是两种最常见的加密技术&#xff0c;它们被广泛应用于数据安全领域&#xff0c;并且可以组合起来以达到更好的加密效果。本文将探讨这两种技术的区别&#xff0c;以及它们在…

OpenHarmony 快速集成轻量级存储(dataPreferences)工具类

OpenHarmony中也有类似于Android的SharedPreferences的轻量级存储&#xff0c;名字叫dataPreferences&#xff0c;这里省略介绍&#xff0c;直接放工具类 一.初始化 首先在EntryAbility中对dataPreferences进行初始化操作&#xff0c;初始化的代码如下&#xff1a; import d…

基于Java+SpringMvc+Vue求职招聘系统详细设计实现

基于JavaSpringMvcVue求职招聘系统详细设计实现 &#x1f345; 作者主页 专业程序开发 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; 文章目录 基于JavaSpringMvcVue求职招聘系统详细设计实现一、前言介…

Nacos注册及使用

一、简介 Nacos是阿里云开源的一个服务发现、配置管理和服务鉴权平台&#xff0c;它提供了一种更简单、更便捷、更开放的方式来管理服务&#xff0c;帮助开发者快速实现服务的发现、配置的管理、服务的鉴权等功能。Nacos可以帮助开发者轻松管理微服务应用中的服务提供者、服务…

啊?这也算事务?!

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

GBASE南大通用-GBase 8s数据库日志模式及切换

一、 GBase 8s数据库共有以下 4 种日志模式&#xff1a;无日志模式、缓冲日志模式、无缓冲日志模式、ANSI 模式。详细介绍如下&#xff1a; 1、无日志模式&#xff08;Non logging&#xff09;&#xff1a; 采用无日志模式时&#xff0c;所有 DML 操作都不会被记录到日志中&…

关于表格太大了jupyter无法单次处理的问题

记录下自己的心路历程…耗时耗精力 我用的数据库单个表格就很大&#xff0c;一个表格有30多G&#xff0c;jupyter无法处理这么大的表格&#xff0c;会直接把电脑的进程全部结束掉&#xff0c;结束掉要是能运行成功倒也行啊&#xff0c;然鹅…给我报错说处理不了&#xff0c;罢工…

代码随想录刷题 | Day1

今日学习目标 一、基础 数组 array类 模板类vector 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索引的方式获取到下标下对应的数据。 需要两点注意的是 数组下标都是从0开始的。 数组内存空间的地址是连续的 而且大家如果使用C的话&…

儿童学python语言能做什么,儿童学python哪个机构好

这篇文章主要介绍了儿童学python哪个线上机构好&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 少儿编程python 文章目录 前言 CSP-J与CSP-S少儿编程证书含金量排名&#xff0…

linux实用技巧:ubuntu18.04安装samba服务器实现局域网文件共享

Ubuntu安装配置Samba服务与Win10共享文件 Chapter1 Ubuntu18.04安装配置Samba服务与Win10共享文件一、什么是Samba二、安装Samba1、查看是否有安装samba2、安装samba 三、配置Samba服务1、创建共享目录&#xff08;以samba_workspaces为例&#xff09;2、为samba设置登录用户3、…

Python装饰器的专业解释

装饰器&#xff0c;其实是用到了闭包的原理来进行操作的。 单个装饰器&#xff1a; 以下是一个简单的例子&#xff1a; def outer(func):print("OUTER enter ...")def wrapper(*args, **kwargs):print("调用之前......")result func(*args, **kwargs)p…

亚马逊云科技 re:Invent 2023 产品体验:亚马逊云科技产品应用实践 国赛选手带你看 Elasticache Serverless

抛砖引玉 讲一下作者背景&#xff0c;曾经参加过国内世界技能大赛云计算的选拔&#xff0c;那么在竞赛中包含两类&#xff0c;一类是架构类竞赛&#xff0c;另一类就是 TroubleShooting 竞赛&#xff0c;对应的分别为亚马逊云科技 GameDay 和亚马逊云科技 Jam&#xff0c;想必…

【数学建模美赛M奖速成系列】Matplotlib绘图技巧(三)

Matplotlib绘图技巧&#xff08;三&#xff09; 写在前面7. 雷达图7.1 圆形雷达图7.2 多边形雷达图 8. 极坐标图 subplot9. 折线图 plot10. 灰度图 meshgrid11. 热力图11.1 自定义colormap 12. 箱线图 boxplot 写在前面 终于更新完Matplotlib绘图技巧的全部内容&#xff0c;有…

php 8.4 xdebug扩展编译安装方法

最新版php8.4 xdebug扩展只能通过编译方式安装, pecl是安装不了的, 编译方法如下 下载最新版xdebug git clone https://github.com/xdebug/xdebug.git 却换入xdebug目录执行编译安装xdebug cd xdebug phpize./configure --enable-xdebugmakemake install3. 配置启用xdebug 这…

JavaScript中实现页面跳转的几种常用方法

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍在JavaScript中实现页面跳转的几种常用方法以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题…

Linux:jumpserver资产管理,分配 (4)

jumpserver可以去管理&#xff0c;Linux&#xff0c;Windows和Windows server&#xff0c;数据库&#xff0c;华为,思科等…网络设备 我这里准备了 三个 分别为 centos7 win10 mysql 在jumpserver上等登录的话&#xff0c;还需要他们的用户去登录&#xff0c;这里等会我就一…

1panel使用指南(一)面板安装

一、1panel简介 1Panel是杭州飞致云信息科技有限公司推出的产品 [1]&#xff0c;帮助用户实现快速建站。 [2]是一款现代化、开源的Linux服务器运维管理面板&#xff0c;于2023年3月推出&#xff0c;深度集成WordPress和Halo&#xff0c;一键完成域名绑定、SSL证书配置等操作&a…