【SimpleFunction系列二.2】SpringBoot注解整合Redisson分布式锁

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
全局流量管理 GTM,标准版 1个月
简介: Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。

<div align="center">

   <a href="https://moyifeng.blog.csdn.net/"> <img src="https://badgen.net/badge/MYF/莫逸风CSDN/4ab8a1?icon=rss"></a>

   <a href="https://github.com/1046895947"> <img src="https://badgen.net/badge/MYF/莫逸风GitHub/4ab8a1?icon=github"></a>

</div>


#### 版本说明(代码在GitHub)


基于simple_functions项目1.1.0分支开发


```mermaid

graph LR;

A(1.0.0:初始化hello接口)-->A1;

A1(1.1.0:整合Redis)-->A11;

A11(1.1.1Redis分布式锁)

```

### 1. Redisson


Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。


[【Redisson项目介绍—官方文档】](https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D)


### 2. 整合Redisson


SpringBoot整合Redisson有两种方式1. 程序化配置方式。2. 文件方式配置


1. **程序化配置**


   Redisson程序化的配置方法是通过构建Config对象实例来实现的。例如:

   ```java

   Config config = new Config();

   config.setTransportMode(TransportMode.EPOLL);

   config.useClusterServers()

       //可以用"rediss://"来启用SSL连接

       .addNodeAddress("redis://127.0.0.1:7181");

   ```


2. **文件方式配置**


   Redisson的配置文件可以是或YAML格式。 通过调用`config.fromYAML`方法并指定一个`File`实例来实现读取YAML格式的配置:


   ```java

   Config config = Config.fromYAML(new File("config-file.yaml"));

   RedissonClient redisson = Redisson.create(config);

   ```


#### 2.1 引入Maven依赖


```xml

<!-- Redisson -->

<dependency>

 <groupId>org.redisson</groupId>

 <artifactId>redisson</artifactId>

 <version>3.17.3</version>

</dependency>

```


#### 2.2 自定义配置类


在Redis.config配置类中添加方法


```java

/**

* 构建Redisson操作对象

* @return RedissonClient

*/

@Bean(destroyMethod = "shutdown") // 服务停止后调用 shutdown 方法。

public RedissonClient redissonClient() {

 Config config = new Config();

 // 单机模式。

 config.useSingleServer().setAddress("redis://" + host + ":" + port);

 // 看门狗的默认时间。

 config.setLockWatchdogTimeout(redissonProperties.getLockWatchdogTimeout());

 return Redisson.create(config);

}

```


#### 2.3 新建DistributedLock分布式锁


```java

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface DistributedLock {

   /**

    * 锁的模式:默认自动模式,当参数只有一个使用 REENTRANT 参数多个使用RED_LOCK

    *

    * @return 锁模式

    */

   LockModel lockModel() default LockModel.AUTO;


   /**

    * 增加key的前缀

    * 设计中预将列表数组转化为多个key使用联锁——"#{#apple.getArray()}"——将数组的每一项作为一个锁。

    * "redisson-#{#apple.getArray()}" 显然会被认为是一个字符串,而不会使用联锁,应将前缀放到keyPrefix中

    */

   String keyPrefix() default "";


   /**

    * 如果keys有多个AUTO模式使用红锁

    *

    * @return keys

    */

   String[] keys() default {};


   /**

    * 租赁时间,默认为0取默认配置,-1,为无限续租。

    * @return 租赁时间

    */

   long leaseTime() default 0;


   /**

    * 等待时间,默认为0取默认配置,-1,为一直等待

    * @return 等待时间

    */

   long waitTime() default 0;

}

```


##### 2.3.1 LockModel:锁的模式


默认自动模式,当参数只有一个使用 REENTRANT,参数多个使用RED_LOCK


```java

public enum LockModel {

   /**

    * 可重入锁

    */

   REENTRANT,

   /**

    * 公平锁

    */

   FAIR,

   /**

    * 联锁

    */

   MULTIPLE,

   /**

    * 红锁

    */

   RED_LOCK,

   /**

    * 读锁

    */

   READ,

   /**

    * 写锁

    */

   WRITE,

   /**

    * 自动模式,当参数只有一个使用 REENTRANT 参数多个 RED_LOCK

    */

   AUTO

}

```


##### 2.3.2 keyPrefix设计意义


```

@DistributedLock(keys = "redisson-#{#apple.getArray()}")

```


如上的key设置,转换后会变成,redisson-a,b,无法对key进行分割为多个key。


```

@DistributedLock(keys = "#{#apple.getArray()}",keyPrefix = "redisson")

```


如果key设置为上方的方式,key将会被解析为redisson:a,redisson:b,的红锁。


##### 2.3.3 keys


可设置为数组,多个key将使用红锁,key的解析过程增加了TemplateParserContext表达式解析上下文,即"redisson-#{#apple.getArray()}"这种表达式只有#{}里面的内容会被作为SpEL表达式解析。


##### 2.3.4 leaseTime:租赁时间


默认值为0,0则取配置文件默认配置,-1则是使用看门狗机制一直续期。


##### 2.3.5 waitTime:等待时间


默认值为0,0则取配置文件默认配置,-1则代表一直等待。


#### 2.4 分布式锁AOP


实现DistributedLockAop,主要是解析keys的SpEL表达式,然后根据注解配置,对方法进行增强。


**DistributedLockAop.java**


```java

@Aspect

@Component

@RequiredArgsConstructor

public class DistributedLockAop {

 /**

    * 日志

    */

 private final Logger log = LoggerFactory.getLogger(getClass());

 /**

    * SpEL表达式

    */

 private final ExpressionParser parser = new SpelExpressionParser();

 /**

    * 表达式解析上下文,只有#{}里的内容才会被作为SqEL表达式解析

    */

 private final TemplateParserContext parserContext = new TemplateParserContext();


 private final RedissonProperties redissonProperties;

 private final RedissonClient redissonClient;


 /**

    * 切入点

    *

    * @param distributedLock 分布式锁注解

    */

 @Pointcut("@annotation(distributedLock)")

 public void log(DistributedLock distributedLock) {

 }


 @Around(value = "log(distributedLock)", argNames = "proceedingJoinPoint,distributedLock")

 public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint, DistributedLock distributedLock) throws Throwable {

   String[] keys = distributedLock.keys();

   if (keys.length == 0) {

     throw new LockException("keys不能为空");

   }

   String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) proceedingJoinPoint.getSignature()).getMethod());

   Object[] args = proceedingJoinPoint.getArgs();


   long leaseTime = distributedLock.leaseTime() == 0 ? redissonProperties.getDefaultLeaseTime() : distributedLock.leaseTime();

   long waitTime = distributedLock.waitTime() == 0 ? redissonProperties.getDefaultWaitTime() : distributedLock.waitTime();

   // 锁模式

   LockModel lockModel = distributedLock.lockModel();

   if (lockModel.equals(LockModel.AUTO)) {

     lockModel = Optional.ofNullable(redissonProperties.getDefaultLockModel())

       .orElse(keys.length > 1 ? LockModel.RED_LOCK : LockModel.REENTRANT);

   }

   if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.RED_LOCK) && keys.length > 1) {

     throw new LockException("参数有多个,锁模式为 -> " + lockModel.name() + ".无法锁定");

   }

   log.debug("锁模式 -> {},等待锁定时间 -> {}秒.锁定最长时间 -> {}秒", lockModel.name(), waitTime / 1000, leaseTime / 1000);


   boolean res = false;

   RLock rLock = null;

   switch (lockModel) {

     case FAIR:

       rLock = redissonClient.getFairLock(getValueBySpEL(keys[0], distributedLock.keyPrefix(), parameterNames, args).get(0));

       break;

     case RED_LOCK:

       List<RLock> rLocks = new ArrayList<>();

       for (String key : keys) {

         List<String> valueBySpEL = getValueBySpEL(key, distributedLock.keyPrefix(), parameterNames, args);

         for (String s : valueBySpEL) {

           rLocks.add(redissonClient.getLock(s));

         }

       }

       RLock[] locks = new RLock[rLocks.size()];

       int index = 0;

       for (RLock r : rLocks) {

         locks[index++] = r;

       }

       rLock = new RedissonRedLock(locks);

       break;

     case MULTIPLE:

       rLocks = new ArrayList<>();


       for (String key : keys) {

         List<String> valueBySpEL = getValueBySpEL(key, distributedLock.keyPrefix(), parameterNames, args);

         for (String s : valueBySpEL) {

           rLocks.add(redissonClient.getLock(s));

         }

       }

       locks = new RLock[rLocks.size()];

       index = 0;

       for (RLock r : rLocks) {

         locks[index++] = r;

       }

       rLock = new RedissonMultiLock(locks);

       break;

     case REENTRANT:

       List<String> valueBySpEL = getValueBySpEL(keys[0], distributedLock.keyPrefix(), parameterNames, args);

       //如果SpEL表达式是数组或者LIST 则使用红锁

       if (valueBySpEL.size() == 1) {

         rLock = redissonClient.getLock(valueBySpEL.get(0));

       } else {

         locks = new RLock[valueBySpEL.size()];

         index = 0;

         for (String s : valueBySpEL) {

           locks[index++] = redissonClient.getLock(s);

         }

         rLock = new RedissonRedLock(locks);

       }

       break;

     case READ:

       RReadWriteLock readLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], distributedLock.keyPrefix(), parameterNames, args).get(0));

       rLock = readLock.readLock();

       break;

     case WRITE:

       RReadWriteLock writeLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], distributedLock.keyPrefix(), parameterNames, args).get(0));

       rLock = writeLock.writeLock();

       break;

   }


   //执行aop

   String clasName = proceedingJoinPoint.getTarget().getClass().getName();

   String methodName = proceedingJoinPoint.getSignature().getName();

   if (rLock != null) {

     try {

       if (waitTime == -1) {

         res = true;

         //一直等待加锁

         rLock.lock(leaseTime, TimeUnit.MILLISECONDS);

       } else {

         res = rLock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);

       }

       if (res) {

         return proceedingJoinPoint.proceed();

       } else {

         throw new LockException(String.format("%s.%s -- 获取锁失败", clasName, methodName));

       }

     } finally {

       if (res) {

         rLock.unlock();

       }

     }

   }

   throw new LockException(String.format("%s.%s -- 创建锁失败", clasName, methodName));

 }



 /**

    * 解析SpEL表达式

    *

    * @param key            key

    * @param keyPrefix      前缀

    * @param parameterNames 参数列表

    * @param values         值

    * @return keys

    */

 private List<String> getValueBySpEL(String key, String keyPrefix, String[] parameterNames, Object[] values) {

   List<String> keys = new ArrayList<>();

   keyPrefix = StringUtils.hasLength(keyPrefix) ? keyPrefix + ":" : "";

   if (!key.contains("#")) {

     String resultKey = redissonProperties.getRedisNameSpace() + ":" + keyPrefix + key;

     keys.add(resultKey);

     return keys;

   }

   //SpEL上下文

   EvaluationContext context = new StandardEvaluationContext();

   for (int i = 0; i < parameterNames.length; i++) {

     context.setVariable(parameterNames[i], values[i]);

   }

   Expression expression = parser.parseExpression(key, parserContext);

   Object value = expression.getValue(context);

   if (value != null) {

     // 数组列表使用联锁,key中不能含有杂质,"redisson-#{#apple.getArray()}" 显然会被认为是一个字符串,应将前缀放到keyPrefix中

     if (value instanceof List<?> valueList) {

       for (Object o : valueList) {

         keys.add(redissonProperties.getRedisNameSpace() + ":" + keyPrefix + o.toString());

       }

     } else if (value.getClass().isArray()) {

       Object[] obj = (Object[]) value;

       for (Object o : obj) {

         keys.add(redissonProperties.getRedisNameSpace() + ":" + keyPrefix + o.toString());

       }

     } else {

       keys.add(redissonProperties.getRedisNameSpace() + ":" + keyPrefix + value);

     }

   }

   log.debug("SpEL表达式key = {},value = {}", key, keys);

   return keys;

 }


}

```


[【Redisson配置—官方文档】](https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95)


### 3. 概念解析


[【SimpleFunction系列二.1】渐进式理解Redis分布式锁](https://blog.csdn.net/qq_38723677/article/details/126132697)


[【SimpleFunction系列二.2】SpringBoot注解整合Redisson分布式锁](https://blog.csdn.net/qq_38723677/article/details/126307218)


[【SimpleFunction系列二.3】Redisson分布式锁8种锁模式剖析]()





相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2天前
|
负载均衡 NoSQL 算法
Redisson分布式锁数据一致性解决方案
通过以上的设计和实现, Redisson能够有效地解决分布式环境下数据一致性问题。但是, 任何技术都不可能万无一失, 在使用过程中还需要根据实际业务需求进行逻辑屏障的设计和错误处理机制的建立。
76 48
|
2月前
|
NoSQL Java Redis
Springboot使用Redis实现分布式锁
通过这些步骤和示例,您可以系统地了解如何在Spring Boot中使用Redis实现分布式锁,并在实际项目中应用。希望这些内容对您的学习和工作有所帮助。
213 83
|
1月前
|
安全
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
Redisson 的看门狗机制是解决分布式锁续期问题的核心功能。当通过 `lock()` 方法加锁且未指定租约时间时,默认启用 30 秒的看门狗超时时间。其原理是在获取锁后创建一个定时任务,每隔 1/3 超时时间(默认 10 秒)通过 Lua 脚本检查锁状态并延长过期时间。续期操作异步执行,确保业务线程不被阻塞,同时仅当前持有锁的线程可成功续期。锁释放时自动清理看门狗任务,避免资源浪费。学习源码后需注意:避免使用带超时参数的加锁方法、控制业务执行时间、及时释放锁以优化性能。相比手动循环续期,Redisson 的定时任务方式更高效且安全。
86 24
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
|
27天前
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
本文深入剖析了Redisson中可重入锁的释放锁Lua脚本实现及其获取锁的两种方式(阻塞与非阻塞)。释放锁流程包括前置检查、重入计数处理、锁删除及消息发布等步骤。非阻塞获取锁(tryLock)通过有限时间等待返回布尔值,适合需快速反馈的场景;阻塞获取锁(lock)则无限等待直至成功,适用于必须获取锁的场景。两者在等待策略、返回值和中断处理上存在显著差异。本文为理解分布式锁实现提供了详实参考。
71 11
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
|
1月前
|
NoSQL Java Redis
springboot怎么使用Redisson
通过以上步骤,已经详细介绍了如何在Spring Boot项目中使用Redisson,包括添加依赖、配置Redisson、创建配置类以及使用Redisson实现分布式锁和分布式集合。Redisson提供了丰富的分布式数据结构和工具,可以帮助开发者更高效地实现分布式系统。通过合理使用这些工具,可以显著提高系统的性能和可靠性。
173 34
|
1月前
|
存储 Java 文件存储
🗄️Spring Boot 3 整合 MinIO 实现分布式文件存储
本文介绍了如何基于Spring Boot 3和MinIO实现分布式文件存储。随着应用规模扩大,传统的单机文件存储方案难以应对大规模数据和高并发访问,分布式文件存储系统成为更好的选择。文章详细讲解了MinIO的安装、配置及与Spring Boot的整合步骤,包括Docker部署、MinIO控制台操作、Spring Boot项目中的依赖引入、配置类编写及工具类封装等内容。最后通过一个上传头像的接口示例展示了具体的开发和测试过程,强调了将API操作封装成通用工具类以提高代码复用性和可维护性的重要性。
218 7
🗄️Spring Boot 3 整合 MinIO 实现分布式文件存储
|
24天前
|
JSON 前端开发 Java
Spring MVC常用的注解
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中 的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 @Controller:控制器的注解,表示是表现层,不能用用别的注解代替 @RestController : 组合注解 @Conntroller + @ResponseBody @GetMapping , @PostMapping , @Put
|
24天前
|
Java Spring
Spring Boot的核心注解是哪个?他由哪几个注解组成的?
Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 : ● @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能; ● @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项 ● @ComponentScan:Spring组件扫描
|
26天前
|
Java 测试技术 Spring
SpringBoot+@Async注解一起用,速度提升
本文介绍了异步调用在高并发Web应用性能优化中的重要性,对比了同步与异步调用的区别。同步调用按顺序执行,每一步需等待上一步完成;而异步调用无需等待,可提升效率。通过Spring Boot示例,使用@Async注解实现异步任务,并借助Future对象处理异步回调,有效减少程序运行时间。
|
10天前
|
人工智能 缓存 自然语言处理
保姆级Spring AI 注解式开发教程,你肯定想不到还能这么玩!
这是一份详尽的 Spring AI 注解式开发教程,涵盖从环境配置到高级功能的全流程。Spring AI 是 Spring 框架中的一个模块,支持 NLP、CV 等 AI 任务。通过注解(如自定义 `@AiPrompt`)与 AOP 切面技术,简化了 AI 服务集成,实现业务逻辑与 AI 基础设施解耦。教程包含创建项目、配置文件、流式响应处理、缓存优化及多任务并行执行等内容,助你快速构建高效、可维护的 AI 应用。