Redis分布式锁详细介绍
来源:脚本之家
时间:2022-12-31 18:42:17 302浏览 收藏
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《Redis分布式锁详细介绍》,聊聊Redis分布式锁,我们一起来看看吧!
分布式锁
在单进程应用中,当一段代码同一时间内只能由一个线程执行时,
多线程下可能会出错,例如两个线程同时对一个数字做累加,两个线程同时拿到了该数字,例如40,一个线程加了10,一个线程加了20,正确结果应该是70,
但由于两个线程在自己的内存中一个算出的是50,一个算出的是60,此时二者都将自己的结果往该数字原本的地方写(保存),
这时候,肯定会有一个线程的值会被覆盖,因为读取->计算->保存 并不是原子操作(原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就会一直运行结束,中间不会有任何线程切换),
也就是说最终的结果要么是50,要么是60,而不可能是70(出现并发或并行的情况下,这种情况大概率会发生),
单进程应用发生这种情况时,可以由程序提供的锁语义直接上锁(例如java中的sychornized)保证该段代码只会被一个线程执行,按照顺序来进行,结果将是正确的。
在分布式应用中,由于一台机器上可能跑着相同的应用进程,或者在不同的机器上跑着,原本程序自带的语义锁已经无法起到作用,
因为相同的代码可能是在不同的机器、进程中执行,所以此时需要一个能够让不同机器、进程中,相同的应用代码执行到同一段代码时,也能够按照顺序执行(或者同一时间内只有一个线程能够执行),
这就需要用到中间件来协调,可以实现分布式锁的中间件有很多,redis就是其中一个。
redis实现分布式锁的原理
redis中分布式锁的原理其实就是在redis当中设置一个值(当然要保证分布式应用连的都是同一个redis,以这个redis作为中间点,否则当然是没用的),这个值只能由一个线程来存放,当其他线程(或者不同机器上的进程)也来存放时,发现这个值已经存在了,就说明此时已经有人在用这把锁了,这时候要么进行重试等待,要么进行放弃。
设置一般使用 SETNX (set if not exists) 指令,如果该值没有,则进行设置,有了则不设置,这就是拿锁的关键了,当拿到锁的人执行处理完毕后,再调用 DEL 执行进行锁的释放。
死锁问题
使用 SETNX 和 DEL 实现了分布式锁,但是有一种情况,如果一个线程进行了 SETNX 拿到锁成功后,突然这个线程因为某种原因崩溃了,导致没有进行 DEL 释放锁,
那么此时,将会导致其他所有的应用都再也没办法拿到这把锁,也就是 死锁 ,这个问题的解决方式是将锁设置一个有效期,到了有效期之后该锁将被自动释放,
使用 EXPIRE 可以给锁设置一个有效期,如下
SETNX LOCK-KEY-NAME true EXPIRE LOCK-KEY-NAME 5
但是还有一个问题,因为 SETNX 和 EXPIRE 是分为两个指令执行的,这中间依然有可能出现 SETNX 执行完毕后,由于认为或者机器、程序发生的故障 导致 EXPIRE 没有执行成功,此时还是有可能会发生死锁,
事务能不能解决这个问题?
NO,因为 EXPIRE 是依赖 SETNX 的执行结果执行的,只有 SETNX 成功后,才能进行 EXPIRE,否则是不可以执行的,事务里并没有 if else 的分支逻辑,要么全部执行,要么一个都不执行。
在 redis2.8 的版本中,引入了set指令的拓展参数,可以让 SETNX 和 EXPIRE 同时执行(原子),解决了超时问题,
SET LOCK-KEY-NAME true ex 5 nx
超时问题
上面虽然说到利用给锁设置过期时间解决可能会发生的死锁问题,但是万一我的程序代码执行时间超过了设置的过期时间,这时候锁自动释放了,但是我的代码还没执行完毕,其他人又进行执行了,导致结果出错怎么办?
在一般的开发场景中,我们会尽量将锁的时间设置的长一些,例如60s,一般应用程序在60s内都能执行完毕,但是怕就怕的是较真,如果60s内也执行不完怎么办?
此时可以使用一种续期的方案,就是当程序在执行过程中,不断的判断锁是否快要过期,代码是否执行完毕,如果快过期了没有执行完毕,就将这把锁进行续期,保证锁不会被自动释放,直到我们的代码执行完毕为止,这种方案在java中由一个叫做 redisson 的框架实现了,可以直接引入使用。
锁误放问题
在锁的使用过程中,很有可能出现其他应用没有拿到锁,但是也执行了 DEL 指令,将我们正在执行中的程序的锁释放了,导致其他地方拿到锁,进入代码段开始执行,
这里的解决方式是,在SETNX的时候,可以将value设置成一个随机生成并全局唯一的一串数字或字符,该线程一直持有字符,在释放锁的时候,将字符与锁中的字符进行比对,如果匹配,则可以进行释放锁,如果不匹配,说明是其他人误放,此时拒绝释放,
但是判断字符是否相同与释放锁并不是原子操作,redis也并不提供这么一种命令,所以我们考虑使用lua脚本执行这几步操作(redisson也实现了),
最重要的一点是,程序中使用释放锁的入口一定要统一,万一有的应用程序不使用上面所述的方法释放,直接使用 DEL ,那么上面说的方案就没用了(笔者为了测试,直接用DEL释放过)。
可重入性
可重入性是指线程在持有锁的情况下,再次请求加锁,如果一个锁支持同一个线程的多次加锁,那么这个锁就是可重入的,Java中的 ReentrantLock 就是可重入锁,大致的原理就是每次获取到锁后对一个数字进行 +1,每次释放的时候进行 -1,当数字为0时,分布式锁被释放,
redis锁如果要支持可重入性,也需要用上面的方式进行支持,不过该逻辑加重了复杂性,一般推荐将需要锁的代码段进行逻辑调整,避免重复获取分布式锁来处理。(当然redisson也支持了可重入锁)
Redlock
上面的方式看起来没有太多的问题了,但是由于redis本身可能也会发生问题,例如在Sentinel集群中,主节点挂掉,从节点变成主节点,但是客户端这时候是不知道的,
如果客户端在刚刚挂掉的主节点上SETNX成功了,但是这把锁还没有同步到从节点中,从节点这时候变成了主节点,这时候新主节点中没有这把锁的信息,
此时另一个客户端来请求这把锁,直接 SETNX 成功,又导致了两个客户端同时在执行相同的代码,又出现了不安全性,
为此业界提供了叫做 Redlock 的解决方案,原理大致是,提供多台redis实例,这些实例之间相互独立,没有主从关系(没有任何关系),同其他分布式算法一样,使用了大多数机制,
加锁时,它会向过半节点发出 set(key, value, nx = True, ex = xxx)指令,只要过半节点设置成功,这把锁就算拿到了,释放锁时向所有节点发出 DEL 指令,
Redlock算法(Redisson已支持)需要考虑出错重试,时钟漂移等等细节问题,同时Redlock需要向多个节点进行读写,性能将要比单例redis下降,
如果业务场景对错误的发生容忍度很低,又可以接受性能稍微有点下降,可以考虑采用Redlock算法。
今天带大家了解了Redis分布式锁的相关知识,希望对你有所帮助;关于数据库的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
-
182 收藏
-
215 收藏
-
302 收藏
-
214 收藏
-
317 收藏
-
342 收藏
-
361 收藏
-
159 收藏
-
164 收藏
-
221 收藏
-
156 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习