# 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.hqwc.cn/news/447834.html

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

相关文章

拓扑排序算法

操作对象&#xff1a;AOV网的点和边 有向无环图&#xff1a;有向图且不会形成回路 AOV网&#xff1a;在一个表示工程的有向图中&#xff0c;用顶点表示活动&#xff0c;用弧表示活动之间的优先关系&#xff0c;这样的有向图为顶点表示活动的网&#xff0c;称为AOV网 拓扑排序…

Web3生态系统:构建去中心化的数字社会

随着科技的飞速发展&#xff0c;我们正处在迈向数字未来的道路上&#xff0c;而Web3生态系统则成为这一变革的中心。不仅仅是技术的演进&#xff0c;Web3代表着对传统互联网体系的颠覆&#xff0c;致力于构建一个去中心化的数字社会。本文将深入探讨Web3的核心特征、对金融、社…

2024年美赛A题:资源可用性和性别比例 Resource Availability and Sex Ratios 思路模型代码解析

2024年美赛A题&#xff1a;资源可用性和性别比例 Resource Availability and Sex Ratios 思路模型代码解析 【点击最下方群名片&#xff0c;加入群聊&#xff0c;获取更多思路与代码哦~】 问题翻译 虽然一些动物物种存在于通常的雄性或雌性之外&#xff0c;但大多数物种在很大…

Linux系统Shell脚本-----------正则表达式 文本三剑客之---------grep、 sed

一、正则表达式 1.前言 正则表达式(regular expression)描述了一种字符串匹配的模式&#xff08;pattern&#xff09;&#xff0c;可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。在Linux中也就是代表我们定义的模式模板&…

“错误: 不支持发行版本 XX”的解决方法

目录 1 查看Project Structure 1.1 打开Project Structure 1.2 查看Project Setting/Project 1.3 查看 Project Setting/Modules 1.4 查看Platform Setting/SDKs 2 查看Settings 2.1 打开Settings 2.2 查看Java Compiler 2.3 查看Maven/Runner 3 查看pom.xml 1 查…

AI算力专题:华为算力分拆:全球AI算力的第二极

今天分享的是AI算力系列深度研究报告&#xff1a;《AI算力专题&#xff1a;华为算力分拆&#xff1a;全球AI算力的第二极》。 &#xff08;报告出品方&#xff1a;华西计算机团队&#xff09; 报告共计&#xff1a;53页 全球龙头英伟达业绩持续高度景气&#xff0c;印证全球A…

k8s安装dashboard报错CrashLoopBackOff

报错信息 使用kubectl get pods -A查看集群&#xff0c;出现错误&#xff1a; kubernetes-dashboard kubernetes-dashboard-xxxxxxxxxx6-2qrst 0/1 CrashLoopBackOff 6 15m查看日志后&#xff0c;发现原因&#xff1a; panic: Get "https://10…

架构整洁之道-组件构建原则

5 组件构建原则 大型软件系统的架构过程与建筑物修建很类似&#xff0c;都是由一个个小组件组成的。所以&#xff0c;如果说SOLID原则是用于指导我们如何将砖块砌成墙与房间的&#xff0c;那么组件构建原则就是用来指导我们如何将这些房间组合成房子的。 5.1 组件 组件是软件的…

MySQL索引的原理和SQL优化策略

1. 索引 在InnoDB存储引擎中&#xff0c;索引分为聚簇索引和辅助索引两种类型。 聚簇索引是指基于表的主键构建的索引&#xff0c;它决定了表中数据的物理存储顺序。也就是说&#xff0c;聚簇索引中的键值按照主键的顺序来排序&#xff0c;并且每个叶子节点存储的是整个表行的…

基于SpringBoot Vue超市管理系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

某赛通电子文档安全管理系统 UploadFileList 任意文件读取漏洞复现

0x01 产品简介 某赛通电子文档安全管理系统(简称:CDG)是一款电子文档安全加密软件,该系统利用驱动层透明加密技术,通过对电子文档的加密保护,防止内部员工泄密和外部人员非法窃取企业核心重要数据资产,对电子文档进行全生命周期防护,系统具有透明加密、主动加密、智能…

离线安装nginx_银河麒麟系统_nginx报错_503_500 Internal Server Error----nginx工作笔记007

如果报这个错误,意思就是,对于nginx.conf文件中指定的,文件夹没有权限 那么这个是去给对应的文件夹赋权限: chmod 777 /opt/module/test_web 就可以了,然后再去访问就不会报错了,还有 503的错误都可以这样解决 然后关于离线安装nginx,尝试了一下如果把之前安装过的nginx,直接…