# Redis 分布式锁如何自动续期
Redis 分布式锁如何自动续期
何为分布式
- 分布式,从狭义上理解,也与集群差不多,但是它的组织比较松散,不像集群,有一定组织性,一台服务器宕了,其他的服务器可以顶上来。分布式的每一个节点,都完成不同的业务,一个节点宕了,这个业务就不可访问了。
- 分布式是指将一个业务拆分不同的子业务,分布在不同的机器上执行。
分布式锁
- 为了保证操作共享资源在高并发情况下的同一时间只能被同一个线程执行,在单体应用单机部署的情况下,可以使用
Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制,这是在JVM层面的加锁方式。 - 单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨
JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题

- 分布式锁是一种用于在分布式系统中实现互斥访问的机制。它可以确保在多个节点同时访问共享资源时,只有一个节点能够获取到锁并执行操作,其他节点需要等待。
分布式锁的实现方式
基于数据库
- 可以使用数据库的事务机制来实现分布式锁。通过在数据库中创建一个特定的表或记录来表示锁的状态,当节点需要获取锁时,尝试插入或更新这个表或记录,如果成功则获取到锁,否则等待。
基于缓存
- 可以使用分布式缓存如
Redis或Memcached来实现分布式锁。通过在缓存中设置一个特定的键值对来表示锁的状态,当节点需要获取锁时,尝试设置这个键值对,如果成功则获取到锁,否则等待。
基于ZooKeeper
ZooKeeper是一个分布式协调服务,可以用于实现分布式锁。通过创建临时顺序节点来表示锁的状态,当节点需要获取锁时,尝试创建自己的临时顺序节点,并检查是否是最小的节点,如果是则获取到锁,否则监听前一个节点的删除事件,等待。
基于分布式算法
- 还有一些基于分布式算法的实现方式,如
Chubby、Raft等。这些算法通过选举、协调等机制来实现分布式锁。
需要注意的是,分布式锁的实现需要考虑到并发性、可靠性和性能等方面的问题,选择合适的实现方式需要根据具体的需求和场景进行评估。
分布式锁的特点
- 互斥性:在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 容错性:只要大部分的
Redis节点正常运行,客户端就可以加锁和解锁。 - 可重入性:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
Redis实现分布式锁
Redis Setnx命令
Redis Setnx(SET if Not eXists)命令在指定的key不存在时,为key设置指定的值。
redis 127.0.0.1:6379> SETNX KEY_NAME VALUE
- 设置成功,返回
1。 设置失败,返回0。
Set命令
-
setnx不能同时完成expire设置失效时长,不能保证setnx和expire的原子性。我们可以使用set命令完成setnx和expire的操作,并且这种操作是原子操作。 -
例子:设置
lock=test,失效时长3s,不存在时设置set lock test ex 3 nx。设置成功返回OK,设置失败返回null
SpringBoot使用Redis分布式锁
基于RedisTemplate
- 假设业务代码块在
6s之内处理完成,那么下面的代码就不会有业务代码执行超时,分布式锁没有问题 - 如果业务代码执行耗时较长,那么设置的键会自动过期,导致上个业务还没有执行结束,下个业务还能拿到锁,分布式锁失效
/*** Set 实现分布式锁子*/
@Override
public void setRedisLock() {// redis KeyString redisKey = "ID_1001";// value 身份标识String redisValue = UUID.randomUUID().toString();try {// 获取分布式锁,设置超时时间 6s 假设业务代码最长 6s 执行完毕ValueOperations valueOperations = redisTemplate.opsForValue();boolean lockFlag = !valueOperations.setIfAbsent(redisKey, redisValue, 6, TimeUnit.SECONDS).booleanValue();if (lockFlag) {throw new Exception("redis key:" + redisKey + " 值:" + redisValue + " 获取锁失败");} else {logger.info("redis key:{} 值:{} 获取锁成功", redisKey, redisValue);}// 实现业务代码:暂时假设业务代码执行时长在 6s 之内} catch (Exception e) {logger.error(e.getMessage(), e);throw new RuntimeException(e.getMessage());} finally {boolean deleteFlag;String currentValue = (String) redisTemplate.opsForValue().get(redisKey);if (redisValue.equals(currentValue)) {deleteFlag = redisTemplate.opsForValue().getOperations().delete(redisKey).booleanValue();if (deleteFlag) {logger.info("redis 锁:{} 释放成功", redisKey);} else {logger.error("redis 锁:{} 释放失败", redisKey);}} else {logger.error("redis 锁:{} 值:{} 身份校验失败无法释放", redisKey, redisValue);}}}
Redis分布式锁续期处理
- 在上面的例子中,当业务代码执行耗时超过
redis设置的超时时间时,下一个任务获取锁的时候还是会获取成功,这样在业务上是又问题的。所以得要考虑处理锁续期。 - 实现思路,开启一个定时任务作为守护线程,如果业务代码没有执行完成主动进行续期操作
- 任务完整之后终止守护线程,释放获取的锁
@Override
public void setRedisLock1() {// redis KeyString redisKey = "ID_1001";TestTask testTask = new TestTask();CustomResponse response = execute(testTask, redisKey, 7, true);if (response.getCode() != 0) {logger.error("线程:" + Thread.currentThread().getId() + "执行结果:" + response.getMsg());}
}/*** 利用redis做分布式锁** @param runnable 执行的业务* @param lockKey 锁定key, 不同业务应该全局唯一* @param lockTime 锁定时间 (单位 ms)* @param autoRelock 是否自动续期*/
public CustomResponse execute(Runnable runnable, String lockKey, long lockTime, boolean autoRelock) {CustomResponse customResponse = new CustomResponse();execute(runnable, lockKey, lockTime, autoRelock, customResponse);return customResponse;
}/*** 利用redis做分布式锁** @param runnable 执行的业务* @param lockKey 锁定key, 不同业务应该全局唯一* @param lockTime 锁定时间 (单位 ms)* @param autoRelock 是否自动续期* @param customResponse 执行结果*/
public void execute(Runnable runnable, String lockKey, long lockTime, boolean autoRelock, CustomResponse customResponse) {if (customResponse == null) {throw new IllegalArgumentException("customResponse 参数不能为空");}if (lockTime <= 0) {throw new IllegalArgumentException("请设置正确的 redis key 超时时间");}boolean flag = true;boolean completedFlag = true;TimerTask timerTask = null;ScheduledFuture<?> scheduledFuture = null;try {// 失效时间,设置失败的key强制删除Long hasKeyExpire = redisTemplate.getExpire(lockKey);if (hasKeyExpire != null && hasKeyExpire.intValue() == -1) {redisTemplate.delete(lockKey);}ValueOperations<String, String> operations = redisTemplate.opsForValue();if (Boolean.TRUE.equals(operations.setIfAbsent(lockKey, "1", lockTime, TimeUnit.MILLISECONDS))) {// 开启续期,超时时间之后开始任务if (autoRelock) {timerTask = new TimerTask() {public void run() {logger.info("redis key:{} 自动续期任务执行...", lockKey);redisTemplate.opsForValue().setIfPresent(lockKey, "1", lockTime, TimeUnit.MILLISECONDS);}};try {scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(timerTask, lockTime / 2, lockTime, TimeUnit.SECONDS);} catch (Throwable e) {logger.debug(e.getMessage());}}customResponse.setMsg(0, "获取 redis 锁成功");// 执行业务逻辑try {runnable.run();// 处理标志位completedFlag = false;} catch (Throwable e) {logger.error("redis key:{} 执行业务代码出错:{}", lockKey, e.getMessage(), e);customResponse.setMsg(500, e.getMessage());}} else {flag = false;customResponse.setMsg(100, "获取锁失败");}} catch (Throwable e) {if (completedFlag) {logger.error(e.getMessage(), e);customResponse.setMsg(500, e.getMessage());}} finally {try {// 删除自己设置的锁if (flag) {redisTemplate.delete(lockKey);logger.info("执行完成删除自己的 key");}// 移除定时任务timerTask.cancel();if (Objects.nonNull(scheduledFuture)) {scheduledFuture.cancel(true);}} catch (Throwable e) {logger.debug(e.getMessage(), e);}}
}private class TestTask implements Runnable {@Overridepublic void run() {try {logger.info("任务开始执行...");Thread.sleep(10000);logger.info("任务执行结束...");} catch (InterruptedException e) {throw new RuntimeException(e);}}}
- 获取锁:使用
Redis的SETNX命令尝试获取锁。如果返回1表示获取锁成功,返回0表示锁已被其他进程持有。 - 设置锁的过期时间:如果成功获取到锁,可以使用
Redis的EXPIRE命令设置锁的过期时间,确保在一定时间后自动释放锁。 - 续期处理:在业务处理过程中,可以定期(比如锁过期时间的一半)使用
Redis的EXPIRE命令来延长锁的过期时间,防止锁过期后被其他进程获取。 - 释放锁:在业务处理完成后,使用
Redis的DEL命令释放锁。 - 需要注意的是,分布式锁的续期处理需要保证原子性,避免多个进程同时续期导致锁被误释放。可以使用
Redis的Lua脚本来保证续期操作的原子性。 另外,为了防止进程异常退出或崩溃导致锁无法释放,可以使用Redis的SET命令设置一个唯一的锁标识,并在获取锁和续期操作时进行比对,确保只有持有锁的进程才能释放锁。
相关文章:
# Redis 分布式锁如何自动续期
Redis 分布式锁如何自动续期 何为分布式 分布式,从狭义上理解,也与集群差不多,但是它的组织比较松散,不像集群,有一定组织性,一台服务器宕了,其他的服务器可以顶上来。分布式的每一个节点&…...
数据结构 归并排序详解
1.基本思想 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。 将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序…...
服务器C盘突然满了,是什么问题
随着时代的发展、互联网的普及,加上近几年云计算服务的诞生以及大规模普及,对于服务器的使用目前是非常普遍的,用户运维的主要对象一般也主要是服务器方面。在日常使用服务器的过程中,我们也会遇到各式各样的问题。最近就有遇到用…...
【深度学习】ND4J-科学计算库
目录 简介 基础用法 基础信息 数组创建 打印数组 变更维度&堆叠 加减乘除 累加/最大/最小 转换操作 矩陈乘法 索引/迭代 深拷贝/引用传递/视图 引用传递 视图 深拷贝 其它 简介 ND4J主要是JVM的科学计算库,内置了很多计算方法,目的…...
2024-01-29 ubuntu 用脚本设置安装交叉编译工具链路径方法,设置PATH环境变量
一、设置PATH环境变量的方法,建议用~/.bash_profile的方法,不然在ssh登录的时候可能没有设置PATH. 二、下面的完整的脚本,里面的echo "export PATH$build_toolchain_path:\$PATH" >> $HOME/.bashrc 就是把交叉编译路径写写到.bashrc设置…...
今年春节很多年轻人选择不买战袍,减少年货置办,「极简过年」,如何看待此现象?
近年来,春节期间出现了一种新的现象,越来越多的年轻人选择不买战袍,减少年货置办,采用“极简过年”的方式度过春节。对于这一现象,不同人有不同的看法。 首先,这种极简过年的方式符合当前社会的一些价值观…...
C语言·贪吃蛇游戏(下)
上节我们将要完成贪吃蛇游戏所需的前置知识都学完了,那么这节我们就开始动手写代码了 1. 程序规划 首先我们应该规划好我们的代码文件,设置3个文件:snack.h 用来声明游戏中实现各种功能的函数,snack.c 用来实现函数,t…...
Flask 入门2:路由
1. 前言 在上一节中,我们使用到了静态路由,即一个路由规则对应一个 URL。而在实际应用中,更多使用的则是动态路由,它的 URL是可变的。 2. 定义一个很常见的路由地址 app.route(/user/<username>) def user(username):ret…...
【C++】 C++入门— 基于范围的 for 循环
C 基于范围的for循环1 使用样例2 使用条件3 完善措施 Thanks♪(・ω・)ノ谢谢阅读!下一篇文章见!!! 基于范围的for循环 1 使用样例 使用for循环遍历数组,我们通常这么写: …...
C++——析构函数
C——析构函数 什么是析构函数 析构函数是C中的一个特殊的成员函数,它在对象生命周期结束时被自动调用,用于执行对象销毁前的清理工作。析构函数特别重要,尤其是在涉及动态分配的资源(如内存、文件句柄、网络连接等)…...
Vue3学习记录(二)--- 组合式API之计算属性和侦听器
一、计算属性 1、简介 计算属性computed(),用于根据依赖的响应式变量的变化,进行自动的计算,并返回计算后的结果。当依赖的响应式变量发生变化时,computed()会自动进行重新计算,并返回最新的计算结果。如果依赖的…...
react-virtualized实现行元素不等高的虚拟列表滚动
前言: 当一个页面中需要接受接口返回的全部数据进行页面渲染时间,如果数据量比较庞大,前端在渲染dom的过程中需要花费时间,造成页面经常出现卡顿现象。 需求:通过虚拟加载,优化页面渲染速度 优点࿱…...
Linux系统各目录作用
/etc文件系统 /etc 目录包含各种系统配置文件,下面说明其中的一些。其他的你应该知道它们属于哪个程序,并阅读该程序的m a n页。许多网络配置文件也在/etc 中。 1. /etc/rc或/etc/rc.d或/etc/rc?.d 启动、或改变运行级时运行的脚本或脚本的目录。 2. /…...
嵌入式系统学习(一)
嵌入式现状(UP经历): 大厂的招聘要求: 技术栈总结: 产品拆解网站: 52audio 方案查询网站iotku,我爱方案网, 主要元器件类型:...
重写Sylar基于协程的服务器(3、协程模块的设计)
重写Sylar基于协程的服务器(3、协程模块的设计) 重写Sylar基于协程的服务器系列: 重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar) 重写Sylar基于协程的服务器(1、日志模…...
Linux之系统安全与应用续章
目录 一. PAM认证 1.2 初识PAM 1.2.1 PAM及其作用 1.2.2 PAM认证原理 1.2.3 PAM认证的构成 1.2.4 PAM 认证类型 1.2.5 PAM 控制类型 二. limit 三. GRUB加密 /etc/grub.d目录 四. 暴力破解密码 五. 网络扫描--NMAP 六. 总结 一. PAM认证 1.2 初识PAM PAM是Linux系…...
《HTML 简易速速上手小册》第7章:HTML 多媒体与嵌入内容(2024 最新版)
文章目录 7.1 在HTML中嵌入视频和音频7.1.1 基础知识7.1.2 案例 1:嵌入视频文件7.1.3 案例 2:嵌入音频文件7.1.4 案例 3:创建一个视频和音频混合的播放列表 7.2 使用 <iframe> 嵌入外部内容7.2.1 基础知识7.2.2 案例 1:嵌入…...
【CSS】移动端适配
移动端适配怎么做? 适配的目的是在屏幕大小不同的终端设备拥有统一的界面,让拥有更大屏幕的终端展示更多的内容。 meta viewport (视口) 移动端初始视口的大小默认是980px,因为世界上绝大多数PC网页的版心宽度为980px ,如果网页…...
DFS剪枝算法经典题目-挑选
4954. 挑选 - AcWing题库 给定一个包含 n 个正整数 a1,a2,…,an的集合。 集合中可能存在数值相同的元素。 请你从集合中挑选一些元素,要求同时满足以下所有条件: 被选中元素不少于 2 个。所有被选中元素之和不小于 l 且不大于 r。所有被选中元素之中最大…...
考研经验总结——考试期间
文章目录 一、订房二、看考场三、休息四、考前带宾馆的书五、安全 一、订房 我刚刚看了看,是9.10号订的酒店。你们可以提前向学长学姐打听你的考场在哪个学校(徐州的考生,考省外的学校是在矿大考试,考省内的学校是在江师大&#…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
Visual Studio Code 扩展
Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后,命令 changeCase.commands 可预览转换效果 EmmyLua…...
CSS3相关知识点
CSS3相关知识点 CSS3私有前缀私有前缀私有前缀存在的意义常见浏览器的私有前缀 CSS3基本语法CSS3 新增长度单位CSS3 新增颜色设置方式CSS3 新增选择器CSS3 新增盒模型相关属性box-sizing 怪异盒模型resize调整盒子大小box-shadow 盒子阴影opacity 不透明度 CSS3 新增背景属性ba…...
对象回调初步研究
_OBJECT_TYPE结构分析 在介绍什么是对象回调前,首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光…...
