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

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

mhr18 2024-10-22 12:37 23 浏览 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分布式锁的其他问题和解决方案。

相关推荐

mysql一 基本操作(mysql基础操作命令)

先讲数据库--mysql、redis、MongoDB爬虫今天的内容:mysql一基本操作注意事项:1.安装:自己安装,有问题可以问老师、可以自己找办法解决2.上课:先讲知识点再回答问题3....

香港DM德馬景泰藍文科集團20250702期《生命的收獲》LIN JING DE

香港DM德馬景泰藍文科集團20250702期《生命的收獲》DMSIXSEVEN/LINJINGDE/1973主編供圖:香港DM德馬數字甲骨文集團Hongkong-basedDMDelm...

01.Java发展历史(java发展简史)

1.Java发展历史Java由SunMicrosystems公司(现为Oracle公司)的JamesGosling及其团队在1991年开发,最初命名为"Oak",后改名为"...

月薪最高12000!看看这些宝山岗位有没有你心仪的~

招聘目录信息1猎上网络科技(上海)有限公司职位分析师职位分析师助理2上海祥腾金属材料有限公司商务车驾驶员3上海赋拓物联网技术有限公司软件测试工程师4上海集优标五高强度紧固件有限公司...

69岁的Java之父终于退休了,他竟然也经历过职场PUA

有些程序员不到35岁就要担心被优化,但有的程序员干到了69岁的高龄,才准备退休。就前几天,Java之父詹姆斯·高斯林,自个儿在领英上宣布以后要美美享受退休生活了。这一下子,就炸出了不少...

ocm认证年薪多少?(ocm认证好考吗)

从业数据库方面的工程师待遇都很好,年薪30万都是比较平常的事。OCM认证的价值可以归纳成以下几点:1、可以全面掌握Oracle知识,专业知识完善;2、证书对于公司招投标有巨大作用,甚至可以不上班就拿钱...

自学java开发攻略,想做程序员的必备攻略?

背景现阶段,随着互联网的快速发展,程序员这个行业越来月吃香,不仅仅是因为科技先进,说出去很牛逼,让别人产生崇拜,主要原因是程序员行业薪资待遇好,相比国内其他行业,程序员的薪资待遇是羡慕了很多人呢!于是...

SpringBoot WebFlux整合R2DBC实现数据库反应式编程

环境:Springboot2.4.12R2DBC简介SpringdataR2DBC是更大的Springdata系列的一部分,它使得实现基于R2DBC的存储库变得容易。R2DBC代表反应式关系数...

AI编码工具未能代替码农:大模型的局限性揭秘

随着ChatGPT和GithubCopilot等AI编码工具的兴起,StackOverflow近日因流量减少宣布裁员近三分之一。这引发了一个争议的问题:ChatGPT这类AI编码工具,真的要颠覆整...

大模型无法替代码农!GPT-4解决GitHub编程问题成功率为0

编辑:编辑部【新智元导读】ChatGPT之类的AI编码工具来势汹汹,StackOverflow又裁员了!不过,普林斯顿和芝大竟发现,面对真实世界GitHub问题,GPT-4的解决率竟是0%。Stac...

机器人动作领域突破!UniVLA模型实现跨模态AI机器人动作统一

还记得《星际穿越》中那台能在无重力、尘暴甚至巨浪中切换形态、随时理解库珀指令的TARS多功能机器人吗?在银幕里,我们羡慕TARS能听懂人话、看懂环境、马上给出最合适的动作;而在现实世界的实验室里,“让...

FEA-Bench:首个仓库级新功能实现基准,让大模型更懂软件开发

当前,大语言模型在代码生成领域已展现出惊人的能力,但能否胜任真实软件开发中的“新增功能实现”任务,仍是一个关键未解的问题。对此,微软亚洲研究院与北京大学联合发布了首个专注于仓库级新功能实现的基准测试...

基于Java的软件版本信息管理系统-2025计算机毕业设计

基于Java的软件版本信息管理系统-2025计算机毕业设计前言随着移动互联网的快速发展,APP已成为人们日常生活中不可或缺的一部分。为了高效地管理这些APP的信息,如版本更新、用户反馈、下载量统计等,...

马斯克在干嘛?抱着它现身推特 简介改为“推特老板”

  马斯克收购推特的交易真是“没完没了”。据彭博社报道,当地时间26日,美国亿万富翁埃隆·马斯克抱着水槽走进了推特公司的旧金山总部,他还将自己在推特账号的公开自我介绍改为“推特老板”(ChiefTw...

福布斯公布7月全球十大富豪:马斯克成6月最大“输家”,身家缩水160亿美元

7月日,福布斯今日公布了2025年7月全球十大富豪榜,其中第二名迎来新面孔。软件巨头甲骨文(Oracle)联合创始人兼董事长拉里埃里森(LarryEllison)凭借公司股价飙升成功上位。由...

取消回复欢迎 发表评论: