# Redis 分布式锁如何自动续期

Redis 分布式锁如何自动续期

何为分布式

  • 分布式,从狭义上理解,也与集群差不多,但是它的组织比较松散,不像集群,有一定组织性,一台服务器宕了,其他的服务器可以顶上来。分布式的每一个节点,都完成不同的业务,一个节点宕了,这个业务就不可访问了。
  • 分布式是指将一个业务拆分不同的子业务,分布在不同的机器上执行。

分布式锁

  • 为了保证操作共享资源在高并发情况下的同一时间只能被同一个线程执行,在单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcoksynchronized)进行互斥控制,这是在JVM层面的加锁方式。
  • 单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题
    在这里插入图片描述
  • 分布式锁是一种用于在分布式系统中实现互斥访问的机制。它可以确保在多个节点同时访问共享资源时,只有一个节点能够获取到锁并执行操作,其他节点需要等待。

分布式锁的实现方式

基于数据库

  • 可以使用数据库的事务机制来实现分布式锁。通过在数据库中创建一个特定的表或记录来表示锁的状态,当节点需要获取锁时,尝试插入或更新这个表或记录,如果成功则获取到锁,否则等待。

基于缓存

  • 可以使用分布式缓存如RedisMemcached来实现分布式锁。通过在缓存中设置一个特定的键值对来表示锁的状态,当节点需要获取锁时,尝试设置这个键值对,如果成功则获取到锁,否则等待。

基于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设置失效时长,不能保证setnxexpire的原子性。我们可以使用set命令完成setnxexpire的操作,并且这种操作是原子操作。

  • 例子:设置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);}}}
  • 获取锁:使用RedisSETNX命令尝试获取锁。如果返回1表示获取锁成功,返回0表示锁已被其他进程持有。
  • 设置锁的过期时间:如果成功获取到锁,可以使用RedisEXPIRE命令设置锁的过期时间,确保在一定时间后自动释放锁。
  • 续期处理:在业务处理过程中,可以定期(比如锁过期时间的一半)使用RedisEXPIRE命令来延长锁的过期时间,防止锁过期后被其他进程获取。
  • 释放锁:在业务处理完成后,使用RedisDEL命令释放锁。
  • 需要注意的是,分布式锁的续期处理需要保证原子性,避免多个进程同时续期导致锁被误释放。可以使用RedisLua脚本来保证续期操作的原子性。 另外,为了防止进程异常退出或崩溃导致锁无法释放,可以使用RedisSET命令设置一个唯一的锁标识,并在获取锁和续期操作时进行比对,确保只有持有锁的进程才能释放锁。

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

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

相关文章

搭建 prometheus + grafana + springboot3 监控

下载安装包 下载prometheus&#xff1a;https://github.com/prometheus/prometheus/releases/download/v2.42.0/prometheus-2.42.0.windows-amd64.zip 下载grafana&#xff1a; https://dl.grafana.com/enterprise/release/grafana-enterprise-9.4.1.windows-amd64.zip Spr…

Python中容器类型的数据

目录 序列 序列的索引操作 加和乘操作 切片操作 成员测试 列表 创建列表 追加元素 插入元素 替换元素 删除元素 元组 创建元组 元组拆包 集合 创建集合 修改集合 字典 创建字典 修改字典 访问字典视图 遍历字典 若我们想将多个数据打包并且统一管理&…

Cloudreve个人网盘系统源码 支持云存储(七牛、阿里云OSS、腾讯云COS、又拍云、OneDrive) 基于Go框架

现在的网盘动不动就限速&#xff0c;涨价&#xff0c;弄得很是心烦。今天分享一款开源免费的网盘项目&#xff0c;基于 Go 语言开发的 Cloudreve。Cloudreve基于Go框架云存储个人网盘系统源码支持多家云存储驱动&#xff08;从机、七牛、阿里云 OSS、腾讯云 COS、又拍云、OneDr…

Win10 双网卡实现同时上内外网

因为需要同时上内网和外网&#xff0c;但公司做了网络隔离&#xff0c;不能同时上内外网&#xff0c;所以多加了块无线网卡&#xff0c;配置双网关实现同时上内外网&#xff0c;互不影响 打开 Windows PowerShell&#xff08;管理员&#xff09;&#xff0c;输入&#xff1a;ro…

翻译: GPT-4 Vision通过量身定制的推荐来增强应用的用户体验 升级Streamlit五

GPT-4 Vision 系列: 翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式一翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式二翻译: GPT-4 Vision静态图表转换为动态数据可视化 升级Streamlit 三翻译: GPT-4 Vision从图像转换为完全可编辑的表格 升级St…

【新书推荐】3.2节 位运算之加减乘除

本节内容&#xff1a;二进制移位运算&#xff0c;以及逻辑运算与算术运算之间的转换。任何进制的位运算本质都是一样的。 ■二进制数移位运算&#xff1a;二进制数向左移位运算相当于做2的幂乘法运算&#xff0c;二进制数向右移位运算&#xff0c;相当于做2的幂除法运算。 ■十…

MySQL安全(一)权限系统

一、授权 1、创建用户 在MySQL中&#xff0c;管理员可以通过以下命令创建用户&#xff1a; namelocalhost IDENTIFIED BY password; name是要创建的用户名&#xff0c;localhost表示该用户只能从本地连接到MySQL&#xff0c;password是该用户的密码。如果要允许该用户从任何…

【深度优先搜索】【组合数学】【动态规划】1467.两个盒子中球的颜色数相同的概率

作者推荐 【动态规划】【字符串】【行程码】1531. 压缩字符串 本文涉及知识点 动态规划汇总 深度优先搜索 组合数学 LeetCode1467 两个盒子中球的颜色数相同的概率 桌面上有 2n 个颜色不完全相同的球&#xff0c;球上的颜色共有 k 种。给你一个大小为 k 的整数数组 balls …

启发式搜索(A*、IDDFS、IDA*)

我们在解决图问题的时候&#xff0c;通常需要使用DFS和BFS搜索&#xff0c;可是这两种搜索方式的效率较低&#xff0c;我们会遍历到很多空白节点&#xff0c;有没有办法可以优化这种低效问题呢&#xff1f;今天要推出我们的主角&#xff1a;启发式搜索。 一、A* 什么是A*算法…

MySQL索引原理以及SQL优化

案例 struct index_failure_t{int id;string name;int cid;int score;string phonenumber;}Map<int,index_failure>; 熟悉C的同学知道&#xff0c;上述案例中&#xff0c;我们map底层是一颗红黑树&#xff0c;一个节点存储了一对kv&#xff08;键值对&#xff09;&…

seata 分布式

一、下载安装seata 已经下载好的朋友可以跳过这个步骤。这里下载的是seata1.6.1这个版本。 1、进入seata官网 地址&#xff1a; https://seata.io/zh-cn/index.html 2、进入下载 3、点击下载地址 下载地址&#xff1a; https://github.com/seata/seata 二、配置seata 进入c…

git仓库批量备份

git的mirror参数 在git中&#xff0c;--mirror是一个用于克隆和推送操作的参数。它用于创建一个镜像仓库&#xff0c;包含了源仓库的所有分支、标签和提交历史记录。 当使用git clone --mirror <source-repo>命令时&#xff0c;会创建一个完全相同的镜像仓库&#xff0…

虚拟机设置静态ip

有时候搭环境需要局域网&#xff0c;设置一下虚拟机静态ip&#xff0c;这里做个记录&#xff1a; 这里我用的是ubuntu18.04的虚拟机&#xff0c;安装完成之后&#xff0c;点击进入设置 这里设置一下桥接模式 这个时候输入ifconfig&#xff0c;就是和主机一个网段了&#xff…

跟着cherno手搓游戏引擎【14】封装opengl

本节先把代码粘上&#xff0c;后续会慢慢把注释都给加上&#xff0c;先看代码了解个大概&#xff08;待更新&#xff09; 前置&#xff1a; RendererAPI.h: #pragma once namespace YOTO {enum class RendererAPI {None 0,OpenGL1};class Renderer {public:inline static R…

JavaWeb后端登录校验功能(JWT令牌技术,Cookie技术,Session,拦截技术,过滤器)

目录 一.登录校验功能&#xff08;解决直接通过路径访问&#xff09; 1.实现思路 二.会话技术 ​编辑 1.Cookie技术 2.Session 3.令牌技术 1.简介 2.如何生成和解析 3.令牌的使用 三.Filter过滤器 1.什么是过滤器 2.实现步骤&#xff1a; 3.过滤器执行流程 4.拦截路径 5.过…

[C++历练之路]C++中的继承小学问

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; &#x1f354;前言&#xff1a; C中&#xff0c;继承是一种面向对象编程的重要概念&#xff0c;它允许一个类&#xff08;子类/派生类&#xff09;从另一个类&#xff08;父类/基类&#xff09;继承属性和方法。继承是…

Hadoop-MapReduce-YarnChild启动篇

一、源码下载 下面是hadoop官方源码下载地址&#xff0c;我下载的是hadoop-3.2.4&#xff0c;那就一起来看下吧 Index of /dist/hadoop/core 二、上下文 在上一篇<Hadoop-MapReduce-MRAppMaster启动篇>中已经将到&#xff1a;MRAppMaster的启动&#xff0c;那么运行M…

(刷题记录)移除元素

我的代码&#xff1a; class Solution {public int removeElement(int[] nums, int val) {int j0;for(int i0;i<nums.length;i){if(nums[i]!val){nums[j]nums[i];j;}}return j;} }思路&#xff1a;双指针&#xff0c;右指针指向当前要处理的元素&#xff0c;有不等的数就赋…

Docker私有仓库搭建

registry私有仓库 步骤一&#xff1a;先拉取registry的镜像 [rootlocalhost ~]#docker pull registry 步骤二&#xff1a;修改docker的配置文件重启 [rootlocalhost ~]#vim /etc/docker/daemon.json {"insecure-registries": ["192.168.66.66:5000"] }[r…

浅谈隔离放大器

浅谈隔离放大器 定义&#xff1a;隔离放大器是将输入的电量信号或物理量信号通过一种技术手段处理后,隔离输出一组模拟量信号,这组模拟量信号是以标准的4-20mA/0-20mA/0-10mA/0-10V/0-5V/1-5V/2-10V/0-2.5V/0-20mA/0-10mA/0-10V/0-100mV/0-5V等信号,以便控制系统及仪器仪表设备…