SpringBoot第44讲:SpringBoot集成Redis - Redis分布式锁的实现之Jedis(setNXPX+Lua)
SpringBoot第44讲:SpringBoot集成Redis - Redis分布式锁的实现之Jedis(setNXPX+Lua)
Redis实际使用场景最为常用的还有通过Redis实现分布式锁。本文是SpringBoot第44讲,主要介绍Redis实现分布式锁
文章目录
- SpringBoot第44讲:SpringBoot集成Redis - Redis分布式锁的实现之Jedis(setNXPX+Lua)
- 1、知识准备
- 1.1、什么是分布式锁,分布式锁有哪些实现方式?
- 1.2、Redis的分布式锁有哪些实现方式?
- 2、实现案例
- 2.1、定义Redis的分布式锁类
- 2.2、定义AOP拦截点
- 2.3、定义AOP切面
- 2.4、切面使用
- 3、示例源码
1、知识准备
需要了解为何要用分布式锁,以及分布式锁常见的实现方式;以及如何通过Redis实现分布式锁的几种方式。
1.1、什么是分布式锁,分布式锁有哪些实现方式?
分布式锁相关的内容请参考 分布式系统第四讲:分布式锁及实现方案
1.2、Redis的分布式锁有哪些实现方式?
主要有两种思路
- 单个Redis实例:setnx(key,当前时间+过期时间) + Lua
- Redis集群模式:Redlock
在实现使用时,由于很多redis客户端包含了上述实现方式,我们可以通过redis客户端进行,更多可以看 分布式系统第四讲:分布式锁及实现方案
2、实现案例
本案例主要介绍 基于Jedis客户端下通过: setnx(key, 当前时间+过期时间) + Lua 实现分布式锁
2.1、定义Redis的分布式锁类
(具体看分布式系统 - 分布式锁及实现方案 中Redis实现分布式锁的部分)
加锁: set NX PX + 重试 + 重试间隔
向Redis发起如下命令: SET productId:lock 0xx9p03001 NX PX 30000 其中,"productId"由自己定义,可以是与本次业务有关的id,"0xx9p03001"是一串随机值,必须保证全局唯一(防止删除其他线程的锁,可以使用traceid作为value),“NX"指的是当且仅当key(也就是案例中的"productId:lock”)在Redis中不存在时,返回执行成功,否则执行失败。"PX 30000"指的是在30秒后,key将被自动删除。执行命令后返回成功,表明服务成功的获得了锁。
解锁:采用lua脚本
在删除key之前,一定要判断服务A持有的value与Redis内存储的value是否一致。如果贸然使用服务A持有的key来删除锁,则会误将服务B的锁释放掉。
if redis.call("get", KEYS[1])==ARGV[1] thenreturn redis.call("del", KEYS[1])
elsereturn 0
end
具体的封装类 RedisDistributedLock 如下:
package springboot.redis.jedis.lock.lock;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.params.SetParams;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;/*** @author qiwenjie*/
@Slf4j
public class RedisDistributedLock {/*** lua script for unlock.*/private static final String UNLOCK_LUA;static {StringBuilder sb = new StringBuilder();sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");sb.append("then ");sb.append(" return redis.call(\"del\",KEYS[1]) ");sb.append("else ");sb.append(" return 0 ");sb.append("end ");UNLOCK_LUA = sb.toString();}/*** unique lock flag based on thread local.*/private final ThreadLocal<String> lockFlag = new ThreadLocal<>();private final StringRedisTemplate redisTemplate;public RedisDistributedLock(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public boolean lock(String key, long expire, int retryTimes, long retryDuration) {// use JedisCommands instead of setIfAbsenseboolean result = setRedis(key, expire);// retry if neededwhile ((!result) && retryTimes-- > 0) {try {log.debug("lock failed, retrying..." + retryTimes);Thread.sleep(retryDuration);} catch (Exception e) {return false;}// use JedisCommands instead of setIfAbsenseresult = setRedis(key, expire);}return result;}private boolean setRedis(String key, long expire) {try {RedisCallback<String> redisCallback = connection -> {JedisCommands commands = (JedisCommands) connection.getNativeConnection();String uuid = UUID.randomUUID().toString(); // change to distribute UUID generation.lockFlag.set(uuid);return commands.set(key, uuid, SetParams.setParams().nx().px(expire));};String result = redisTemplate.execute(redisCallback);return !StringUtils.isEmpty(result);} catch (Exception e) {log.error("set redis occurred an exception", e);}return false;}public boolean unlock(String key) {boolean success = false;try {List<String> keys = new ArrayList<>();keys.add(key);List<String> args = new ArrayList<>();args.add(lockFlag.get());// use lua scriptRedisCallback<Long> redisCallback = connection -> {Object nativeConnection = connection.getNativeConnection();if (nativeConnection instanceof JedisCluster) { // cluster modereturn (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);} else if (nativeConnection instanceof Jedis) { // single modereturn (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);}return 0L;};Long result = redisTemplate.execute(redisCallback);success = result != null && result > 0;} catch (Exception e) {log.error("release lock occurred an exception", e);} finally {if (success) {lockFlag.remove();}}return success;}
}
2.2、定义AOP拦截点
定义RedisLock注解
package springboot.redis.jedis.lock.annotation;import java.lang.annotation.*;/*** @author qiwenjie*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RedisLock {/*** redis lock key as value.** @return lock key*/String value() default "";/*** how long we hold the lock.** @return mills*/long expireMills() default 30000;/*** if lock failed, do we need to retry, default retry 0 means NO retry.** @return retry times*/int retryTimes() default 0;/*** when we retry to get lock, what's the duration for next retry.** @return mills*/long retryDurationMills() default 200;
}
2.3、定义AOP切面
定义AOP切面类RedisLockAspect,用来拦截@RedisLock注解方法,并调用RedisDistributedLock对方法加锁处理。
package springboot.redis.jedis.lock.lock;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.springframework.context.annotation.Configuration;
import springboot.redis.jedis.lock.annotation.RedisLock;import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;/*** @author qiwenjie*/
@Slf4j
@Aspect
@Configuration
public class RedisLockAspect {/*** lock impl.*/@Resourceprivate RedisDistributedLock distributedLock;/*** AOP, around PJP.** @param pjp ProceedingJoinPoint* @return Object* @throws Throwable Throwable*/@Around("@annotation(springboot.redis.jedis.lock.annotation.RedisLock)")public Object around(ProceedingJoinPoint pjp) throws Throwable {// get attribute through annotationMethod method = ((MethodSignature) pjp.getSignature()).getMethod();RedisLock redisLock = method.getAnnotation(RedisLock.class);String key = redisLock.value();if (StringUtils.isEmpty(key)) {Object[] args = pjp.getArgs();key = Arrays.toString(args);}// do lockboolean lock = distributedLock.lock(key, redisLock.expireMills(), redisLock.retryTimes(),redisLock.retryDurationMills());if (!lock) {// 加锁失败,不执行业务逻辑log.debug("get lock failed, key: {}", key);return null;}// execute method, and unlocklog.debug("get lock success, key: {}", key);try {// executereturn pjp.proceed();} catch (Exception e) {log.error("execute locked method occurred an exception", e);} finally {// unlockboolean releaseResult = distributedLock.unlock(key);log.debug("release lock: {}, success: {}", key, releaseResult);}return null;}
}
2.4、切面使用
只需要在对应方法添加@RedisLock注解,即可实现分布式锁:
@RedisLock
public void xxxMethod() {}
分布式锁在项目中的使用,可以参考这篇文章
- 项目实战第四十二讲:分布式环境下,使用ResubmitCheck注解进行防重校验
3、示例源码
todo
相关文章:
SpringBoot第44讲:SpringBoot集成Redis - Redis分布式锁的实现之Jedis(setNXPX+Lua)
SpringBoot第44讲:SpringBoot集成Redis - Redis分布式锁的实现之Jedis(setNXPXLua) Redis实际使用场景最为常用的还有通过Redis实现分布式锁。本文是SpringBoot第44讲,主要介绍Redis实现分布式锁 文章目录 SpringBoot第44讲:SpringBoot集成Re…...
STM32F4X USART串口使用
STM32F4X USART串口使用 串口概念起始位波特率数据位停止位校验位串口间接线 STM32F4串口使用步骤GPIO引脚复用函数串口初始化函数串口例程 串口概念 串口是MCU与外部通信的重要通信接口,也是MCU在开发过程中的调试利器。串口通信有几个重要的参数,分别…...
python实现两个字符串比对差异点
一:代码实现 import difflib, re# 比较两个文本差异点 def compare_text_index(text1, text2):# 创建SequenceMatcher对象matcher = difflib.SequenceMatcher(a=text1, b=text2)# 获取差异报告diff_report = matcher.get_opcodes()# 检查差异报告中是否存在关键词错误for tag…...
SQLite数据库实现数据增删改查
当前文章介绍的设计的主要功能是利用 SQLite 数据库实现宠物投喂器上传数据的存储,并且支持数据的增删改查操作。其中,宠物投喂器上传的数据包括投喂间隔时间、水温、剩余重量等参数。 实现功能: 创建 SQLite 数据库表,用于存储宠…...
【Golang系统开发】搜索引擎(2) 压缩词典
写在前面 这篇文章我们就给出一系列的数据结构,使得词典能达到越来越高的压缩比。当然,和倒排索引记录表的大小相比,词典只占据了非常小的空间。那么为什么要对词典进行压缩呢? 这是因为决定信息检索系统的查询响应时间的一个重…...
clickhouse修改默认密码
1.明文密码 vim /etc/clickhouse-server/users.xml找到下面的语句,增加明文密码 <password>123456789</password> 2. sha256密码 # echo -n 123456789 | openssl dgst -sha256 (stdin) 15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225 修改…...
基于java在线捐赠系统设计与实现
摘要 近年来,随着网络的快速发展,由于网络的开放性和便利性,具有广阔的发展前景。 本文设计并实现了医药捐赠系统。通过分析确定由两个不同的用户组成,每个用户具有不同的功能。它还可以帮助用户在线求助、申请项目、发表留言等&a…...
【前端】vscode javascript 代码片段失效问题解决
1. 文件--首选项--用户代码片段-vue.json : 添加 // { // // Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and // // description. Add comma separated ids of the languages where the snippet is app…...
AE-卡通人物解说动画视频的制作
目录 1.导入卡通人物图片和音频文件 2.新建合成 3.在卡通人物图片上添加效果和表达式 4.在音频文件上添加效果和表达式 5.将卡通人物中的 CC Split2 中分割1 表达式链接到滑块中 6.卡通人物根据音频文件自动匹配口型。 AE制作卡通人物解说视频,卡通人物口型根据…...
Linux 查看日志
在 Linux 中,内核日志使用 printk 函数进行输出。你可以通过以下方法查看 printk 的日志: 使用 dmesg 命令: dmesg 这个命令会显示内核环缓冲区中的日志消息,包括使用 printk 输出的消息。你可以通过滚动浏览输出来查看完整的日志…...
使用IO多路复用select完成TCP循环服务器接收客户端消息并打印
服务器 客户端 结果...
unity之Input.GetKeyDown与Input.GetKey区别
文章目录 Input.GetKeyDown与Input.GetKey区别 Input.GetKeyDown与Input.GetKey区别 Input.GetKey 和 Input.GetKeyDown 是 Unity 中用于检测按键状态的两个不同函数。它们之间的区别在于何时触发。 Input.GetKey(KeyCode key): 这个函数会在用户按住指定的键时触发࿰…...
excel 核心快捷键用法
1、wps怎样只复制公示计算出来的数据 1.1、按下快捷键“CtrlC”,复制该单元格。 1.2、按下快捷键“ShiftCtrlV”,即“粘贴为数值”,即可只复制数字而不复制该单元格的公式 1.3、wps怎样只复制公示计算出来的数据_百度知道https://zhidao.baid…...
postgresql
源码安装 ./configure --prefix/apps/pgsql make world -j4 make install-world useradd -s /bin/bash -m -d /home/postgres postgres echo -e ‘123456\n123456’ | passwd postgres mkdir -pv /pgsql/data chown postgres:postgres /pgsql/data/ 设置环境变量 vim /etc/…...
AutoSAR配置与实践(基础篇)3.2 BSW中的I/O架构和模块详解
传送门 -> AUTOSAR配置与实践总目录 AutoSAR配置与实践(基础篇)3.2 BSW中的I/O架构和模块详解 一、 BSW中的I/O架构和模块详解1.1 I/O 模块构成1.2 各子模块功能详解二、举例说明I/O 模块如何配合完成信号采集2.1 硬件处理先行 (step1-4)2.2 AUTOSAR软件登场(step 5-7)2.3…...
基于Java+SpringBoot+Vue的学校田径运动会管理系统【源码+论文+演示视频+包运行成功】
博主介绍:✌擅长Java、微信小程序、Python、Android等,专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇🏻 不然下次找不到哟 Java项目精品实战案…...
使用 Visual Studio Code Docker 工具调试 .NET 容器
作者:Chet Husk 排版:Alan Wang Visual Studio Code Docker 工具已发布1.26.0版本,这个版本为使用 .NET SDK 构建和调试容器映像提供了内置支持。 VS Code 中的 Docker 调试 Visual Studio Code Docker 工具使开发人员可以轻松入门容器。它…...
AI引擎助力,CamScanner智能高清滤镜开启扫描新纪元!
文章目录 ⭐ 写在前面⭐ 突破图像处理难点:扫描全能王的独特优势⭐ 耳听为虚,眼见为实⭐ 产品背后的主要核心:AI-Scan助力⭐ 深度学习助力智能文档处理的国际化进程⭐ 品味智能文档处理的轻松与精准 ⭐ 写在前面 在数字化快速发展的今天&…...
opencv进阶07-支持向量机cv2.ml.SVM_create()简介及示例
支持向量机(Support Vector Machine,SVM)是一种二分类模型,目标是寻找一个标准(称为超平面)对样本数据进行分割,分割的原则是确保分类最优化(类别之间的间隔最大)。当数据…...
LA@n维向量@解析几何向量和线性代数向量
文章目录 概念n维向量向量类型实向量和复向量行向量和列向量行列向量的转换特殊向量向量运算 矩阵的向量分块👺 解析几何向量和线性代数向量👺向量空间 n n n维向量空间 n n n维空间的 n − 1 n-1 n−1维超平面 概念 n维向量 由 n n n个有次序的数 a …...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
