SpringBoot+AOP+Redission实战分布式锁

文章目录

  • 前言
  • 一、Redission是什么?
  • 二、使用场景
  • 三、代码实战
    • 1.项目结构
    • 2.类图
    • 3.maven依赖
    • 4.yml
    • 5.config
    • 6.annotation
    • 7.aop
    • 8.model
    • 9.service
  • 四、单元测试
  • 总结


前言

在集群环境下非单体应用存在的问题:JVM锁只能控制本地资源的访问,无法控制多个JVM间的资源访问,所以需要借助第三方中间件来控制整体的资源访问,redis是一个可以实现分布式锁,保证AP的中间件,可以采用setnx命令进行实现,但是在实现细节上也有很多需要注意的点,比如说获取锁、释放锁时机、锁续命问题,而redission工具能够有效降低实现分布式锁的复杂度,看门狗机制有效解决锁续命问题。


一、Redission是什么?

Redisson是一个用于Java的Redis客户端,它提供了许多分布式对象和服务,使得在Java应用中使用Redis变得更加便捷。Redisson提供了对Redis的完整支持,并附带了一系列的功能,如分布式锁、分布式集合、分布式对象、分布式队列等。


二、使用场景

  1. 分布式锁:Redisson提供了强大的分布式锁机制,通过基于Redis的分布式锁,可以保证在分布式环境下的数据一致性。常见的使用场景包括分布式任务调度、防止重复操作、资源竞争控制等。
  2. 分布式集合:Redisson提供了一系列的分布式集合实现,如Set、List、Queue、Deque等。这些集合的特点是数据分布在多台机器上,并且支持并发访问,适用于需要在多个节点之间共享数据的场景。
  3. 分布式对象:Redisson支持将普通Java对象转换为可在Redis中存储和操作的分布式对象。这些对象可以跨JVM进行传输,并保持一致性。使用分布式对象,可以方便地实现分布式缓存、会话管理等功能。
  4. 分布式队列:Redisson提供了高级的分布式队列实现,支持公平锁和非公平锁的排队方式。分布式队列可以在多个线程和多个JVM之间进行数据传输,适用于消息队列、异步任务处理等场景。
  5. 分布式信号量、锁定和闭锁:Redisson提供了分布式信号量、可重入锁和闭锁等机制,用于实现更复杂的并发控制需求。这些工具能够有效地管理并发访问,确保在分布式环境下的数据操作的正确性。

除了以上提到的功能,Redisson还提供了许多其他的分布式应用场景所需的功能组件,如分布式BitSet、分布式地理位置、分布式发布/订阅等。


三、代码实战

通过aop切面编程,可以降低与业务代码的耦合度,便于拓展和维护

1.项目结构

在这里插入图片描述


2.类图

在这里插入图片描述


3.maven依赖

<dependencies><!-- Spring Boot --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Redisson --><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.20.0</version></dependency><!-- Spring AOP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- Spring Expression Language (SpEL) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency>
</dependencies>

4.yml

dserver:port: 8081spring:redis:host: localhostport: 6379# 如果需要密码认证,请使用以下配置# password: your_password

5.config

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author 28382*/
@Configuration
public class RedissonConfig {@Value("${spring.redis.host}")private String redisHost;@Value("${spring.redis.port}")private int redisPort;// 如果有密码认证,请添加以下注解,并修改相应的配置://@Value("${spring.redis.password}")//private String redisPassword;@Bean(destroyMethod = "shutdown")public RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort);// 如果有密码认证,请添加以下配置://config.useSingleServer().setPassword(redisPassword);return Redisson.create(config);}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @author 28382*/
@Configuration
public class RedisTemplateConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());template.afterPropertiesSet();return template;}
}

6.annotation

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author 28382*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {// 自定义业务keysString[] keys() default {};// 租赁时间 单位:毫秒long leaseTime() default 30000;// 等待时间 单位:毫秒long waitTime() default 3000;}

7.aop

支持解析 SpEL

import com.mxf.code.annotation.DistributedLock;
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.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;@Aspect
@Component
@Slf4j
public class DistributedLockAspect {@Autowiredprivate RedissonClient redissonClient;@Around("@annotation(distributedLock)")public Object lockMethod(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();// 获取自定义业务keysString[] keys = distributedLock.keys();// 租赁时间long leaseTime = distributedLock.leaseTime();// 等待时间long waitTime = distributedLock.waitTime();// 创建参数名发现器ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();// 获取方法参数名String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);// 创建 SpEL 解析器ExpressionParser expressionParser = new SpelExpressionParser();// 创建 SpEL 上下文EvaluationContext evaluationContext = new StandardEvaluationContext();// 设置方法参数值到 SpEL 上下文中Object[] args = joinPoint.getArgs();if (parameterNames != null && args != null && parameterNames.length == args.length) {for (int i = 0; i < parameterNames.length; i++) {evaluationContext.setVariable(parameterNames[i], args[i]);}}// 构建完整的锁键名StringBuilder lockKeyBuilder = new StringBuilder();if (keys.length > 0) {for (String key : keys) {if (StringUtils.hasText(key)) {try {// 解析 SpEL 表达式获取属性值Object value = expressionParser.parseExpression(key).getValue(evaluationContext);lockKeyBuilder.append(value).append(":");} catch (SpelEvaluationException ex) {// 如果解析失败,则使用原始字符串作为属性值LiteralExpression expression = new LiteralExpression(key);lockKeyBuilder.append(expression.getValue()).append(":");}}}}// 使用方法名作为最后一部分键名lockKeyBuilder.append(methodSignature.getName());String fullLockKey = lockKeyBuilder.toString();// 获取 Redisson 锁对象RLock lock = redissonClient.getLock(fullLockKey);// 尝试获取分布式锁// boolean tryLock(long waitTime, long leaseTime, TimeUnit unit)boolean success = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);if (success) {try {// 执行被拦截的方法return joinPoint.proceed();} finally {// 释放锁lock.unlock();}} else {log.error("Failed to acquire distributed lock");// 获取锁超时,抛出异常throw new RuntimeException("Failed to acquire distributed lock");}}}

8.model


import lombok.Data;/*** @author 28382*/
@Data
public class User {private Long id;private String name;private String address;public User(Long id, String name) {this.id = id;this.name = name;}
}

9.service

import com.mxf.code.annotation.DistributedLock;
import com.mxf.code.model.User;
import org.springframework.stereotype.Service;/*** @author 28382*/
@Service
public class UserService {int i = 0;@DistributedLockpublic void test01() {System.out.println("执行方法1 , 当前线程:" + Thread.currentThread().getName() + "执行的结果是:" + ++i);sleep();}@DistributedLock(keys = "myKey",leaseTime = 30L)public void test02() {System.out.println("执行方法2 , 当前线程:" + Thread.currentThread().getName() + "执行的结果是:" + ++i);sleep();}@DistributedLock(keys = "#user.id")public User test03(User user) {System.out.println("执行方法3 , 当前线程:" + Thread.currentThread().getName() + "执行的结果是:" + ++i);sleep();return user;}@DistributedLock(keys = {"#user.id", "#user.name"}, leaseTime = 5000, waitTime = 5000)public User test04(User user) {System.out.println("执行方法4 , 当前线程:" + Thread.currentThread().getName() + "执行的结果是:" + ++i);sleep();return user;}private void sleep() {// 模拟业务耗时try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}}}

四、单元测试

import com.mxf.code.model.User;
import com.mxf.code.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;@SpringBootTest(classes = SpringBootLockTest.class)
@SpringBootApplication
public class SpringBootLockTest {@AutowiredUserService userService;private static final Random RANDOM = new Random();public static void main(String[] args) {SpringApplication.run(SpringBootLockTest.class, args);}@Testpublic void test01() throws Exception {ExecutorService executorService = Executors.newFixedThreadPool(10);Runnable task = () -> userService.test01();for (int i = 0; i < 100; i++) {executorService.submit(task);}Thread.sleep(10000);}@Testpublic void test02() throws Exception {ExecutorService executorService = Executors.newFixedThreadPool(10);Runnable task = () -> userService.test02();for (int i = 0; i < 100; i++) {executorService.submit(task);}Thread.sleep(10000L);}@Testpublic void test03() throws Exception {ExecutorService executorService = Executors.newFixedThreadPool(10);Runnable task = () -> userService.test03(new User(1L, "name"));for (int i = 0; i < 100; i++) {executorService.submit(task);}Thread.sleep(10000L);}@Testpublic void test04() throws Exception {ExecutorService executorService = Executors.newFixedThreadPool(10);Runnable task = () -> userService.test04(new User(1L, "name"));for (int i = 0; i < 100; i++) {executorService.submit(task);}Thread.sleep(100000L);}
}

test01

在这里插入图片描述

test02

在这里插入图片描述

test03

在这里插入图片描述

test04

在这里插入图片描述

总结

可以在项目中单独建立一个Module,需要的子系统直接引入,在需要加分布式的业务代码方法上添加注解及配注解属性值即可,存在一个潜在的问题就是如果redis使用主从架构,在主节点和从节点同步加锁信息时主节点挂掉,这时选取一个暂未同步完整节点信息的从节点作为主节点时,存在一定锁失效的问题,这是可以考虑红锁或者zookeeper实现强一致性。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker容器的基本操作

一、查看Docker的版本信息 [roothuyang1 ~]# docker version 二、查看docker的详细信息 [roothuyang1 ~]# docker info 三、Docker镜像操作 Docker创建容器前需要本地存在对应的镜像&#xff0c;如果本地加载不到相关镜像&#xff0c;Docker默认就会尝试从镜像仓库https://hu…

算法竞赛入门【码蹄集新手村600题】(MT1100-1120)C语言

算法竞赛入门【码蹄集新手村600题】(MT1100-1120&#xff09;C语言 目录MT1101 带参数的宏IIMT1102 长方体MT1103 球体积MT1104 三角形MT1105 英寸英尺英里MT1106 盎司磅MT1107 加仑/升MT1108 保留小数MT1109 和10相比MT1110 最小值MT1111 最大值MT1112 中庸之道MT1113 三人同行…

代码随想录算法训练营第17期第28天 | 93.复原IP地址 、78.子集 、​ 90.子集II

93. 复原 IP 地址 1.当点号有三个之后&#xff0c;当最后一部分数值是有效的话&#xff0c; 就可以加入结果集了 class Solution { public:vector<string> res;bool isvalid(const string& s, int left, int right){if (right < left){return false;}if (s[left]…

-bash: fork: retry: Resource temporarily unavailable 问题解决

错误提示&#xff1a; -bash: fork: retry: Resource temporarily unavailable 错误分析&#xff1a;之前已经出现过这种资源限制的报错提醒&#xff0c;然后整个系统可用的连接数就已经用完了&#xff0c;无法使用工具来获取系统信息&#xff0c;所以将运行的任务脚本kill后开…

【运维工程师学习八】代理及安装配置Nginx反向代理

【运维工程师学习八】代理 正向代理一、使用正向代理的主要作用有&#xff1a;二、反向代理三、使用反向代理的主要作用有&#xff1a;四、透明代理五、各种代理的主要区别六、Nginx的安装七、了解nginx的文件位置八、了解nginx程序的命令行参数九、开启nginx反向代理十、解读n…

less的使用

less的介绍&#xff1a; less使用 1、 less使用的第一种用法&#xff0c;起变量名&#xff0c;变量名区分大小写&#xff1a; 这里我们定义一个粉色变量 我想使用直接把变量拿过来就行 2、vscode使用插件&#xff0c;直接将Css文件转换less文件&#xff1a; 3、第二种用法&…

【力扣每日一题】2023.8.5 合并两个有序链表

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们两个有序的链表&#xff0c;要我们保持升序的状态合并它们。 我们可以马上想要把两个链表都遍历一遍&#xff0c;把所有节点的…

android Android Studio Giraffe | 2022.3.1 版本Lombok不兼容 解决方案

android Android Studio Giraffe | 2022.3.1 版本Lombok不兼容 解决方案 1.查看当前的android studio 版本 Android Studio Giraffe | 2022.3.1 Build #AI-223.8836.35.2231.10406996, built on June 29, 2023 2.打开 idea 官网下载页面 idea下载历史版本 找到对应的版本编号…

MySQL 与MongoDB区别

一、什么是MongoDB呢 ? MongoDB 是由C语言编写的&#xff0c;是一个基于分布式文件存储的开源数据库系统。在高负载的情况下&#xff0c;添加更多的节点&#xff0c;可以保证服务器性能。 MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB 将数据存储为一…

集中/本地转发、AC、AP

1.ADSL ADSL MODEM&#xff08;ADSL 强制解调器&#xff09;俗称ADSL猫 ADSL是一种异步传输模式&#xff08;ATM)。ADSL是指使用电话线上网&#xff0c;需要专用的猫&#xff08;Modem)&#xff0c;在上网的时候高频和低频分离&#xff0c;所以上网电话两不耽误&#xff0c;速…

Vue命名规范

JS文件命名 一般采用的是小驼峰命名法&#xff0c;如 pieChartHelp 第一个单词小写&#xff0c;其他单词首字母大写 Components 文件命名 一般采用的是大驼峰命名法&#xff0c;如PieChart 所有单词的首字母大写 常量命名 一般全部大写&#xff0c;每个单词使用分隔符隔开&…

若依打印sql

官方issue 自动生成的代码&#xff0c;sql日志怎么没有打印 在ruoyi-admin中的application.yml配置如下。 # 日志配置&#xff0c;默认 logging:level:com.ruoyi: debugorg.springframework: warn#添加配置com.ying: debug输出sql

网络基础1

文章目录 网络基础11. 计算机网络背景1.1 网路发展1.2 认识 "协议" 2. 网络协议初识2.1 协议分层2.2 OSI七层模型2.3 TCP/IP五层(或四层)模型协议栈与OS的关系 3. 网络传输基本流程3.1 同一个局域网两台主机通信3.2 同一个路由器的两个子网通信 4. 网络中的地址管理4…

PHP国外在线教育系统源码 在线课程系统源码 直播课程系统源码提供在线课程,现场课程,测验

Proacademy是在线教育一体化的解决方案&#xff0c;用于创建类似于Udemy、Skillshare、Coursera这种在线教育市场。 这个平台提供在线课程&#xff0c;现场课程&#xff0c;测验等等&#xff0c;并有一个基于实际业务需要的高级认证插件&#xff0c;程序基于Laravel强大的安全框…

通过51单片机实现直流电机调速

一、项目背景及目的 随着各种工业生产设备和机械设备的广泛使用&#xff0c;直流电机调速技术的研究和应用越来越受到人们的重视&#xff0c;具有广泛的应用前景。本项目通过51单片机实现直流电机调速功能&#xff0c;为实际工程应用提供一个可靠和有效的调速方案。 二、设计思…

基于LLM的SQL应用程序开发实战(二)

基于LLM的SQL应用程序开发实战(二) 16.2 使用LangChain SQL代理 回到案例应用本身,我们使用“Run All”的方式重新运行一下,让大家看见更多内部的内容,如图16-5所示,因为在VSCode代码编辑器中,可以看见Jupyter关于当前应用的变量(variable)。 图16- 5 查询Jupyter V…

嵌入式:C高级 Day3

一、整理思维导图 二、判断家目录下&#xff0c;普通文件的个数和目录文件的个数 三、输入一个文件名&#xff0c;判断是否为shell脚本文件&#xff0c;如果是脚本文件&#xff0c;判断是否有可执行权限&#xff0c;如果有可执行权限&#xff0c;运行文件&#xff0c;如果没有可…

【SQL应知应会】表分区(四)• Oracle版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 分区表 • Oracle版 前言一、分区表1.什么是表分区…

phpstudy 进行 composer 全局配置

背景 因为注意到&#xff0c;使用 phpStudy 进行环境搭建时&#xff0c;有时需要使用 composer 每次都需要查找资料进行配置&#xff0c; 在此进行记录笔记&#xff0c;方便有需要的道友借鉴 配置 版本&#xff1a;composer1.8.5&#xff08;phpStudy8 当前只能安装这一个版本…

MicroPython ESP32网页实时更新DHT11数据显示

MicroPython ESP32网页实时更新DHT11数据显示 &#x1f4cc;相关篇《MicroPython ESP32 读取DHT11温湿度传感器数据》&#x1f4cd;《【Micropython esp32/8266】网页点灯控制示例》 ✨本例综合以上两篇文章内容实现&#xff1a;在本地网页中显示DHT11温度传感器数据。可以做到…