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

Redis应用篇(众星追月):分布式锁(redis分布式锁的实现原理)

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

本文将介绍Redis高频使用的一个场景——「利用Redis实现分布式锁」

想必大家都知道,在遇到并发问题时,我们通常会使用锁来解决并发问题。

这时,有同学可能说:“这个我会,不就用synchronized、Lock这些实现吗?”

对,你说得不错。但是你只说对了一半,在「传统单机部署」的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。

但是在「分布式系统」中,由于分布式系统「多线程」「多进程」并且「分布在不同机器」上,这将使原单机并发控制锁策略失效,为了解决这个问题就需要一种「跨JVM的互斥机制」来控制共享资源的访问,这就得靠分布式锁啦。

看透锁本质

在我看来:所有的锁本身都可以用一个变量来表示。

比如:在「单机上运行」的多线程程序来说。取一个变量,变量为0时,表示没有线程获取锁;变量为1时,表示已经有线程获取锁。

加锁:线程调用加锁操作,检查变量是否为0,如果为0,表示没线程获取锁,将变量设置为1,表示获取锁;如果不是0,表示其他线程已经暂用锁,获取锁失败。

解说:同理。

而分布式环境下,同样可以以变量形式理解分布式锁。

但是,和线程在单机上操作锁不同的是,在分布式场景下,「锁变量需要由一个共享存储系统来维护」,只有这样,多个客户端才可以通过访问共享存储系统来访问锁变量。相应的,「加锁和释放锁的操作就变成了读取、判断和设置共享存储系统中的锁变量值」

「可见,满足分布式锁的要求」

  • 「锁操作原子性」:分布式锁的加锁和释放锁的过程,涉及多个操作。所以,在实现分布式锁时,我们需要保证这些「锁操作的原子性」
  • 「锁的可靠性」:共享存储系统保存了锁变量,如果共享存储系统发生故障或宕机,那么客户端也就无法进行锁操作了。在实现分布式锁时,我们需要考虑保证「共享存储系统的可靠性」,进而保证「锁的可靠性」

上面我们提到了可以使用一个锁变量来表示锁,其实你也可以理解为「占位」。只不过分布式锁需要把这个坑位拿出来放于「共享」的地方,每个都从「共享处来检查坑位」

占位一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占位。先来先占, 用完了,再调用 del 指令释放茅坑。

//加锁
> setnx lock_key 1
OK
//业务逻辑
>(其他操作)
//释放锁
> del lock_key

但是有个问题,如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样就会「陷入死锁」,锁永远得不到释放。

于是我们在拿到锁之后,再给锁加上一个过期时间,这样即使中间出现异常也可以保证指定时间之后锁会自动释放。

//加锁
> setnx lock_key 1
OK
> expire lock_key 5
//业务逻辑
>(其他操作)
//释放锁
> del lock_key

但是以上逻辑还有问题。如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因为机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。

这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。你也许会想到使用事务什么的执行,但是这里不行,因为如果 setnx 没抢到锁,expire 是不应该执行的。

Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和expire 指令可以一起执行,彻底解决了分布式锁的乱象。

set key value [EX seconds | PX milliseconds] [NX]

除了上述基本常规的问题,还有这些「你可能没考虑到的问题」

超时问题

Redis 的分布式锁不能解决超时问题,如果在加锁和释放锁间的业务逻辑执行时间太长,以至于超出了锁的超时限制,就会出现问题(也就是锁过期了,你的业务逻辑还没执行完)。

因为这时候锁过期了,第二个客户端B重新持有了这把锁,但是紧接着客户端A执行完了业务逻辑,就把锁给释放了,客户端C就会在客户端B逻辑执行完之间拿到了锁。为了避免这个问题,Redis 分布式锁不要用于较长时间的任务。

为了应对这个问题,我们需要能区分来自不同客户端的锁操作,具体咋做呢 ? 针对于这个问题,我们可以想办法把命令略加点小技巧。可以在锁变量的值上想想办法。

在使用SETNX命令进行加锁的方法中,我们通过把锁变量值设置为1或0,表示是否加锁成功。1和0只有两种状态,无法表示究竟是哪个客户端进行的锁操作。

所以,我们在加锁操作时,可以「让每个客户端给锁变量设置一个唯一值」,这里的唯一值就可以用来标识当前操作的客户端。

在释放锁操作时,客户端需要判断,当前「锁变量的值是否和自己的唯一标识相等」,只有在相等的情况下,才能释放锁。这样一来,就不会出现误释放锁的问题了。

于是,我们的命令可以这样写:

//加锁,unique_value作为客户端唯—性的标识
SET lock_key unique_value NX PX 5000

其中,unique_value 是客户端的唯一标识,可以用一个随机生成的字符串来表示,PX 5000则表示 lock_key会在5s后过期,以免客户端在这期间发生异常而无法释放锁。

因为在加锁操作中,每个客户端都使用了一个唯一标识,所以在「释放锁操作」时,我们需要「判断锁变量的值」,是否等于执行释放锁操作的客户端的唯一标识,如下所示,可以使用Lua脚本来保证原子性:

//释放锁比较unique_value是否相等,避免误释放
if redis.call("get" ,KEYS[1])== ARGV[1] then
 return redis.call("del" , KEYS[1])
else
 return 0
end

可重入性

可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加锁,那么这个锁就是可重入的。比如 Java 语言里有个 ReentrantLock 就是可重入锁。

Redis 分布式锁如果要支持可重入,可以对客户端的 set 方法进行包装,使用线程的 Threadlocal 变量存储当前持有锁的计数。

此处就不过多介绍,大抵不会问,有兴趣可以自己上网查阅看书。

课外补充

上述内容,是个基于单个Redis节点实现分布式锁。

当我们要实现「高可靠的分布式锁」时,就不能只依赖单个的命令操作了,我们需要按照一定的步骤和规则进行加解锁操作,否则,就可能会出现锁无法工作的情况。“一定的步骤和规则”是指啥呢?其实就是分布式锁的算法。

这里简单介绍Redlock算法的执行步骤。Redlock算法的实现需要有N个独立的Redis实例。接下来,我们可以分成3步来完成加锁操作。

1、客户端获取当前时间

2、客户端按照顺序在每个Master实例中尝试获得锁。在获得锁的过程中,为每一个锁操作设置一个快速失败时间(如果想要获得一个10秒的锁,那么每一个锁操作的失败时间设为5-50ms)。

这样可以避免客户端与一个已经故障的Master通信占用太长时间,通过快速失败的方式尽快的与集群中的其他节点完成锁操作。

3、客户端计算出与master获得锁操作过程中消耗的时间,「当且仅当Client获得锁消耗的时间小于锁的存活时间,并且在一半以上的master节点中获得锁」。才认为client成功地获得了锁。

4、如果已经获得了锁,「Client执行任务的时间窗口是锁的存活时间减去获得锁消耗的时间。」

5、如果Client获得锁的数量不足一半以上,或获得锁的时间超时,那么认为获得锁失败。客户端「需要尝试在所有的master节点中释放锁, 即使在第二步中没有成功获得该Master节点中的锁,仍要进行释放操作。」

相关推荐

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)凭借公司股价飙升成功上位。由...

取消回复欢迎 发表评论: