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的完整支持,并附带了一系列的功能,如分布式锁、分布式集合、分布式对象、分布式队列等。
二、使用场景
- 分布式锁:Redisson提供了强大的分布式锁机制,通过基于Redis的分布式锁,可以保证在分布式环境下的数据一致性。常见的使用场景包括分布式任务调度、防止重复操作、资源竞争控制等。
- 分布式集合:Redisson提供了一系列的分布式集合实现,如Set、List、Queue、Deque等。这些集合的特点是数据分布在多台机器上,并且支持并发访问,适用于需要在多个节点之间共享数据的场景。
- 分布式对象:Redisson支持将普通Java对象转换为可在Redis中存储和操作的分布式对象。这些对象可以跨JVM进行传输,并保持一致性。使用分布式对象,可以方便地实现分布式缓存、会话管理等功能。
- 分布式队列:Redisson提供了高级的分布式队列实现,支持公平锁和非公平锁的排队方式。分布式队列可以在多个线程和多个JVM之间进行数据传输,适用于消息队列、异步任务处理等场景。
- 分布式信号量、锁定和闭锁: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实现强一致性。
相关文章:
SpringBoot+AOP+Redission实战分布式锁
文章目录 前言一、Redission是什么?二、使用场景三、代码实战1.项目结构2.类图3.maven依赖4.yml5.config6.annotation7.aop8.model9.service 四、单元测试总结 前言 在集群环境下非单体应用存在的问题:JVM锁只能控制本地资源的访问,无法控制…...
Linux系统---进程概念
文章目录 冯诺依曼体系结构操作系统(OS)进程的理解 进程状态 进程优先级 环境变量 进程地址空间 Linux2.6内核进程调度队列 一、冯诺依曼体系结构 我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。 如图…...
ELK常用语法和线上问题排查
ELK常用语法及线上问题排查 速查问题 1.全文搜索 在搜索栏输入目标值,查询所有字段中包含该值的文档 案例:用户反馈了一串id或者其他同事给了一个jobid,还没有头绪怎么查的时候,直接全局搜索 jobid 2.字段 限定字段全文搜索…...
session-cookies 三个缓存 localStorage、sessionStorage、Cookies。
session-cookies session-cookies This plugin is used to summarize the browser’s three caches localStorage, sessionStorage, Cookies.The plugin is designed to be quick and easy to use. Below is a summary of some apis. session-cookies 这个插件是用来汇总浏览…...
LA@行列式性质
文章目录 行列式性质🎈转置不变性质交换性质多重交换移动(抽出插入)👺 因子提取性质拆和性质倍加性质 手算行列式的主要方法原理:任何行列式都可以化为三角行列式 行列式性质🎈 设行列式 ∣ A ∣ d e t ( a i j ) |A|\mathrm{det}(a_{ij}) …...
【java安全】原生反序列化利用链JDK7u21
文章目录 【java安全】原生反序列化利用链JDK7u21前言原理equalsImpl()如何调用equalsImpl()?HashSet通过反序列化间接执行equals()方法如何使hash相等? 思路整理POCGadget为什么在HashSet#add()前要将HashMap的value设为其他值? 【java安全】…...
[SQL智慧航行者] - SQL刷题规划
前言: 不知不觉, 慢慢的把一些基础知识整理到了现在. SQL挖掘机系列可能就要到此结束了. 目前是规划把这部分SQL练习部分放到SQL刷题的专栏, 就不放到SQL专栏了. 因为后续如果接触更多的知识点和题目也好分类. 请大家如果关注的话, 想看SQL知识点 --> SQL专栏; 想看SQL刷…...
概率论与数理统计:第一章:随机事件及其概率
文章目录 概率论Ch1. 随机事件及其概率1.基本概念(1)随机试验、随机事件、样本空间(2)事件的关系和运算①定义:互斥(互不相容)、对立②运算法则:德摩根率 (3)概率的定义(4)概率的性质(5)概率计算排列组合 2.等可能概型1.古典概型 (离散)2.几何概型 (连续…...
elementUi重置Select选择器样式、option、deep、vue3、plus
文章目录 前言select选框样式(element-plus)option选项面板样式(element-plus) 前言 样式标签属性为<style scoped lang"scss"></style> select选框样式(element-plus) // 设置选择框的背景样式 ::v-deep .el-input__wrapper {background: transparent;b…...
jar命令的安装与使用
场景: 项目中经常遇到使用WinR软件替换jar包中的文件,有时候存在WinRAR解压替换时提示没有权限,此时winRAR不能用还有有什么方法替换jar包中的文件。 方法: 使用jar命令进行修改替换 问题: 执行jar命令报错jar 不…...
windows系统多线程多进程下应用函数CloseHandle的一些细节
Windows CloseHandle()函数调用之后,对应的内核对象的句柄计数器减去1。如果此内核对象的句柄的计数器计数变为0,系统会回收此内核对象。所以,如果创建了内核对象(例如可调用了CreateThread创建一个线程内核对象。此对象创建成功后此线程内核…...
微服务——elasticsearch
初识ES——什么是elasticsearch elasticsearch的发展 初识ES——正向索引和倒排索引 初识ES——es与mysql的概念对比 类比到mysql中是表结构约束 概念对比 初始ES——安装es和kibana 1.部署单点es 1.1创建网络 要安装es容器和kibana容器并让他们之间相连,这里…...
paddlenlp:社交网络中多模态虚假媒体内容核查
初赛之环境配置篇 一、背景二、任务三、数据集1、初赛阶段2、评分标准 四、环境操作五、写在最后 一、背景 随着新媒体时代信息媒介的多元化发展,各种内容大量活跃在媒体内中,与此同时各类虚假信息也充斥着社交媒体,影响着公众的判断和决策。…...
Centos系统有哪些特点呢
CentOS 是一个基于 Red Hat Enterprise Linux (RHEL) 的免费的开源操作系统,由 CentOS 项目维护。CentOS 项目是一个社区驱动的项目,旨在为用户提供一个稳定的、可扩展的 Linux 发行版。 CentOS 系统具有以下特点: 稳定性:CentOS 系统非常稳定,即使是在高负载的环境下也是如此…...
一文学会git常用命令和使用指南
文章目录 0. 前言1.分支分类和管理1. 分支分类规范:2. 最佳实践3. 分支命名规范示例:4. 分支管理方法: 2. commit 注释规范1. 提交注释结构:2. 提交注释的准则: 3. git 常用命令1. git pull 核心用法2. git push 命令1…...
[PyTorch][chapter 46][LSTM -1]
前言: 长短期记忆网络(LSTM,Long Short-Term Memory)是一种时间循环神经网络,是为了解决一般的RNN(循环神经网络)存在的长期依赖问题而专门设计出来的。 目录: 背景简介 LSTM C…...
寄存器详解(二)
目录 内存中字的存储 示例: 数据段寄存器DS与[address] 字的传送 数据段简介 CPU提供的栈机制 栈段寄存器SS和栈顶指针寄存器SP PUSH AX指令的完整描述 示例图 POP AX指令的完整描述 示例图 栈顶超界问题 示例一: 示例二: 内存中字…...
Java AIO
在Java中,AIO代表异步I/O(Asynchronous I/O),它是Java NIO的一个扩展,提供了更高级别的异步I/O操作。AIO允许应用程序执行非阻塞I/O操作,而无需使用Selector和手动轮询事件的方式。 与传统的NIO和Java NIO…...
java集合总结
1.常见集合 Collection List:有序可重复集合,可直接根据元素的索引来访问 Vector-StackArrayListLinkedList Queue:队列集合 Deque-LinkedList、ArrayDequePriorityQueue Set:无序不可重复集合,只能根据元素本身来访问…...
list交并补差集合
list交并补差集合 工具类依赖 <dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.8.1</version> </dependency><dependency><groupId>commons-collections&…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...
【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...
在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤: 第一步: 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为: // 改为 v…...
向量几何的二元性:叉乘模长与内积投影的深层联系
在数学与物理的空间世界中,向量运算构成了理解几何结构的基石。叉乘(外积)与点积(内积)作为向量代数的两大支柱,表面上呈现出截然不同的几何意义与代数形式,却在深层次上揭示了向量间相互作用的…...
