当前位置: 首页 > news >正文

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.lryc.cn/news/272287.html

相关文章:

  • Javascript 正则表达式零宽断言
  • Chocolatey
  • 雍禾植发成毛发行业标杆!雍禾医疗获“年度医疗大健康消费企业”
  • Linux内核--进程管理(十二)共享内存和信号量
  • java 构造方法
  • CISSP 第2章: 人员安全和风险管理概念
  • 前端八股文(CSS篇)一
  • 游戏加速器LSP/DLL导致WSL.EXE无法打开问题修复!
  • 宏电股份5G RedCap终端产品助力深圳极速先锋城市建设
  • linux top命令中 cpu 利用率/mem 使用率与load average平均负载计算方式
  • win11出现安全中心空白和IT管理员已限制对某些区域的访问(不一样的解决方式),真实的个人经历,并且解决经过
  • 关于安卓重启设备和重启应用进程
  • Linux内核--进程管理(十三)O(1)调度算法
  • 【QT】发生的运行时错误汇总
  • 机器学习常用算法模型总结
  • 笔记中所得(已删减)
  • 在Django5中使用Websocket进行通信
  • 外汇天眼:CySEC与NAGA Markets Europe达成15万欧元的和解
  • Docker仓库搭建与镜像推送拉取
  • 最适合初学者的PHP集成环境!
  • 添加 Android App Links
  • 五、Spring AOP面向切面编程(基于注解方式实现和细节)
  • ES6 class详解
  • 嵌入式固件加密的几种方式
  • [C#]使用onnxruntime部署Detic检测2万1千种类别的物体
  • 关于Spring @Transactional事务传播机制详解
  • 力扣139.单词拆分
  • Docker 镜像命令总汇
  • 客户服务:助力企业抵御经济衰退的关键要素与策略
  • 第八周:AIPM面试准备