百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis分布式锁:锁的续期,避免锁超时后导致多个线程获得锁

mhr18 2024-10-22 12:37 17 浏览 0 评论

使用现状

Redis分布式锁的基础内容,我们已经在Redis分布式锁:基于AOP和Redis实现的简易版分布式锁这篇文章中讲过了,也在文章中示范了正常的加锁和解锁方法。

分布式锁在之前的项目中一直运行良好,没有辜负我们的期望。

发现问题

但在最近查线上日志的时候偶然发现,有一个业务场景下,分布式锁偶尔会失效,导致有多个线程同时执行了相同的代码。

我们经过初步排查,定位到是因为在这段代码中间调用了第三方的接口导致。

因为业务代码耗时过长,超过了锁的超时时间,造成锁自动失效,然后另外一个线程意外的持有了锁。于是就出现了多个线程共同持有锁的现象。

解决方案

问题既然已经出现了,那么接下来我们就应该考虑解决方案了。

我们也曾经想过,是否可以通过合理地设置LockTime(锁超时时间)来解决这个问题?

但LockTime的设置原本就很不容易。LockTime设置过小,锁自动超时的概率就会增加,锁异常失效的概率也就会增加,而LockTime设置过大,万一服务出现异常无法正常释放锁,那么出现这种异常锁的时间也就越长。我们只能通过经验去配置,一个可以接受的值,基本上是这个服务历史上的平均耗时再增加一定的buff。

既然这条路走不通了,那么还有其他路可以走么?

当然还是有的,我们可以先给锁设置一个LockTime,然后启动一个守护线程,让守护线程在一段时间后,重新去设置这个锁的LockTime。

看起来很简单是不是?

但在实际操作中,我们要注意以下几点:

1、和释放锁的情况一致,我们需要先判断锁的对象是否没有变。否则会造成无论谁持有锁,守护线程都会去重新设置锁的LockTime。不应该续的不能瞎续。

2、守护线程要在合理的时间再去重新设置锁的LockTime,否则会造成资源的浪费。不能动不动就去续。

3、如果持有锁的线程已经处理完业务了,那么守护线程也应该被销毁。不能主人都挂了,守护者还在那里继续浪费资源。

代码实现

我们首先先生成一个内部类去实现Runnable,作为守护线程的参数。

public class SurvivalClamProcessor implements Runnable {

private static final int REDIS_EXPIRE_SUCCESS = 1;

SurvivalClamProcessor(String field, String key, String value, int lockTime) {

this.field = field;

this.key = key;

this.value = value;

this.lockTime = lockTime;

this.signal = Boolean.TRUE;

}

private String field;

private String key;

private String value;

private int lockTime;

//线程关闭的标记

private volatile Boolean signal;

void stop() {

this.signal = Boolean.FALSE;

}

@Override

public void run() {

int waitTime = lockTime * 1000 * 2 / 3;

while (signal) {

try {

Thread.sleep(waitTime);

if (cacheUtils.expandLockTime(field, key, value, lockTime) == REDIS_EXPIRE_SUCCESS) {

if (logger.isInfoEnabled()) {

logger.info("expandLockTime 成功,本次等待{}ms,将重置锁超时时间重置为{}s,其中field为{},key为{}", waitTime, lockTime, field, key);

}

} else {

if (logger.isInfoEnabled()) {

logger.info("expandLockTime 失败,将导致SurvivalClamConsumer中断");

}

this.stop();

}

} catch (InterruptedException e) {

if (logger.isInfoEnabled()) {

logger.info("SurvivalClamProcessor 处理线程被强制中断");

}

} catch (Exception e) {

logger.error("SurvivalClamProcessor run error", e);

}

}

if (logger.isInfoEnabled()) {

logger.info("SurvivalClamProcessor 处理线程已停止");

}

}

}

其中expandLockTime是通过Lua脚本实现的。延长锁超时的脚本语句和释放锁的Lua脚本类似。

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1],ARGV[2]) else return '0' end";

在以上代码中,我们将waitTime设置为Math.max(1, lockTime * 2 / 3),即守护线程许需要等待waitTime后才可以去重新设置锁的超时时间,避免了资源的浪费。

同时在expandLockTime时候也去判断了当前持有锁的对象是否一致,避免了胡乱重置锁超时时间的情况。

然后我们在获得锁的代码之后,添加如下代码:

SurvivalClamProcessor survivalClamProcessor

= new SurvivalClamProcessor(lockField, lockKey, randomValue, lockTime);

Thread survivalThread = new Thread(survivalClamProcessor);

survivalThread.setDaemon(Boolean.TRUE);

survivalThread.start();

Object returnObject = joinPoint.proceed(args);

survivalClamProcessor.stop();

survivalThread.interrupt();

return returnObject;

这段代码会先初始化守护线程的内部参数,然后通过start函数启动线程,最后在业务执行完之后,设置守护线程的关闭标记,最后通过interrupt()去中断sleep状态,保证线程及时销毁。

后续

本文讲解了如何通过启动一个守护线程去重置锁超时时间,也同时介绍了在实现过程的注意点。随带着也科普了一下线程销毁的正确方式。

那么关于分布式锁还有下文么?我也不知道,权当是有吧,可能下一期会讲讲如何通过其他方式(除Redis之外的)去实现分布式锁,也可能是讲一下Redis分布式锁的其他问题和解决方案。

相关推荐

使用 Docker 部署 Java 项目(通俗易懂)

前言:搜索镜像的网站(推荐):DockerDocs1、下载与配置Docker1.1docker下载(这里使用的是Ubuntu,Centos命令可能有不同)以下命令,默认不是root用户操作,...

Spring Boot 3.3.5 + CRaC:从冷启动到秒级响应的架构实践与踩坑实录

去年,我们团队负责的电商订单系统因扩容需求需在10分钟内启动200个Pod实例。当运维组按下扩容按钮时,传统SpringBoot应用的冷启动耗时(平均8.7秒)直接导致流量洪峰期出现30%的请求超时...

《github精选系列》——SpringBoot 全家桶

1简单总结1SpringBoot全家桶简介2项目简介3子项目列表4环境5运行6后续计划7问题反馈gitee地址:https://gitee.com/yidao620/springbo...

Nacos简介—1.Nacos使用简介

大纲1.Nacos的在服务注册中心+配置中心中的应用2.Nacos2.x最新版本下载与目录结构3.Nacos2.x的数据库存储与日志存储4.Nacos2.x服务端的startup.sh启动脚...

spring-ai ollama小试牛刀

序本文主要展示下spring-aiollama的使用示例pom.xml<dependency><groupId>org.springframework.ai<...

SpringCloud系列——10Spring Cloud Gateway网关

学习目标Gateway是什么?它有什么作用?Gateway中的断言使用Gateway中的过滤器使用Gateway中的路由使用第1章网关1.1网关的概念简单来说,网关就是一个网络连接到另外一个网络的...

Spring Boot 自动装配原理剖析

前言在这瞬息万变的技术领域,比了解技术的使用方法更重要的是了解其原理及应用背景。以往我们使用SpringMVC来构建一个项目需要很多基础操作:添加很多jar,配置web.xml,配置Spr...

疯了!Spring 再官宣惊天大漏洞

Spring官宣高危漏洞大家好,我是栈长。前几天爆出来的Spring漏洞,刚修复完又来?今天愚人节来了,这是和大家开玩笑吗?不是的,我也是猝不及防!这个玩笑也开的太大了!!你之前看到的这个漏洞已...

「架构师必备」基于SpringCloud的SaaS型微服务脚手架

简介基于SpringCloud(Hoxton.SR1)+SpringBoot(2.2.4.RELEASE)的SaaS型微服务脚手架,具备用户管理、资源权限管理、网关统一鉴权、Xss防跨站攻击、...

SpringCloud分布式框架&amp;分布式事务&amp;分布式锁

总结本文承接上一篇SpringCloud分布式框架实践之后,进一步实践分布式事务与分布式锁,其中分布式事务主要是基于Seata的AT模式进行强一致性,基于RocketMQ事务消息进行最终一致性,分布式...

SpringBoot全家桶:23篇博客加23个可运行项目让你对它了如指掌

SpringBoot现在已经成为Java开发领域的一颗璀璨明珠,它本身是包容万象的,可以跟各种技术集成。本项目对目前Web开发中常用的各个技术,通过和SpringBoot的集成,并且对各种技术通...

开发好物推荐12之分布式锁redisson-sb

前言springboot开发现在基本都是分布式环境,分布式环境下分布式锁的使用必不可少,主流分布式锁主要包括数据库锁,redis锁,还有zookepper实现的分布式锁,其中最实用的还是Redis分...

拥抱Kubernetes,再见了Spring Cloud

相信很多开发者在熟悉微服务工作后,才发现:以为用SpringCloud已经成功打造了微服务架构帝国,殊不知引入了k8s后,却和CloudNative的生态发展脱轨。从2013年的...

Zabbix/J监控框架和Spring框架的整合方法

Zabbix/J是一个Java版本的系统监控框架,它可以完美地兼容于Zabbix监控系统,使得开发、运维等技术人员能够对整个业务系统的基础设施、应用软件/中间件和业务逻辑进行全方位的分层监控。Spri...

SpringBoot+JWT+Shiro+Mybatis实现Restful快速开发后端脚手架

作者:lywJee来源:cnblogs.com/lywJ/p/11252064.html一、背景前后端分离已经成为互联网项目开发标准,它会为以后的大型分布式架构打下基础。SpringBoot使编码配置...

取消回复欢迎 发表评论: