Redis 分布式锁相关问题

2021-03-05 08:53:53 +08:00
 zhao1014

“有一个稍微安全一点的方案是将 set 指令的 value 设置为一个随机数,释放锁时先匹配随机数是否一致,然后再删除 key,这是为了确保当前线程占有的锁不被其他线程释放,除非这个锁是因为过期自动被服务器释放”

--《 Redis 深度历险》-分布式锁-超时问题

为什么当前线程的锁会被其他线程释放?什么情况下会出现这种情况?这里我有点不理解;如果锁未超时,临界区执行完毕正常释放锁,如果锁超时了,临界区未执行完,由服务器自动释放锁,为什么会有其他线程释放锁的情况?

3820 次点击
所在节点    Redis
14 条回复
kakach
2021-03-05 08:58:41 +08:00
当前线程占有的锁 != 当前线程的锁
liuzhaowei55
2021-03-05 09:04:55 +08:00
http://redis.cn/topics/distlock.html

可以参看这里的说明,主要是为了程序的鲁棒性。

--- 以下内容为复制

为什么基于故障转移的实现还不够
为了更好的理解我们想要改进的方面,我们先分析一下当前大多数基于 Redis 的分布式锁现状和实现方法.

实现 Redis 分布式锁的最简单的方法就是在 Redis 中创建一个 key,这个 key 有一个失效时间( TTL),以保证锁最终会被自动释放掉(这个对应特性 2 )。当客户端释放资源(解锁)的时候,会删除掉这个 key 。

从表面上看,似乎效果还不错,但是这里有一个问题:这个架构中存在一个严重的单点失败问题。如果 Redis 挂了怎么办?你可能会说,可以通过增加一个 slave 节点解决这个问题。但这通常是行不通的。这样做,我们不能实现资源的独享,因为 Redis 的主从同步通常是异步的。

在这种场景(主从结构)中存在明显的竞态:

客户端 A 从 master 获取到锁
在 master 将锁同步到 slave 之前,master 宕掉了。
slave 节点被晋级为 master 节点
客户端 B 取得了同一个资源被客户端 A 已经获取到的另外一个锁。安全失效!
有时候程序就是这么巧,比如说正好一个节点挂掉的时候,多个客户端同时取到了锁。如果你可以接受这种小概率错误,那用这个基于复制的方案就完全没有问题。否则的话,我们建议你实现下面描述的解决方案。
jamsfox
2021-03-05 09:10:37 +08:00
如果所有线程都用同一个锁,会出现下面这种情况:线程 A 加锁并设置超时时间,A 由于一些原因阻塞,key 过期锁释放,线程 B 获取到锁,在 B 获取到锁期间 A 继续执行,最终删掉线程 B 加的锁。

建议看下这篇文章: http://zhangtielei.com/posts/blog-redlock-reasoning.html
freeminder
2021-03-05 09:12:30 +08:00
首先服务器的锁超时其实没法影响到线程本地的超时(个人看法),所以本地释放锁的时机无法严格早于服务器释放锁。释放锁本身也有成本,如果真实的时序是 A 释放锁指令发出-B 服务端超时释放-C 服务端收到释放锁指令并执行。那么 BC 之间有人抢到了锁就导致 A 的释放锁把别人释放了。
另外就这个本地线程或处理过程如何跟服务器锁超时一致的事情,我之前也是没有解法,跟楼看哪位老师指导下。
wakzz
2021-03-05 09:21:09 +08:00
3 楼正解,主要发生在例如 A 、B 两个线程上锁
1. A 对某个 key 上锁成功
2. 异常原因导致 A 网络阻塞,最终 key 锁超时自动释放
3. B 成功对 key 上锁成功
4. 此时 key 锁的所有者是 A,然后 A 恢复正常,尝试对锁操作例如释放锁
5. A 对锁操作时就需要判断当前 key 锁是否还是自己持有,也就是楼主发帖所述的通过随机数匹配来判断
wakzz
2021-03-05 09:22:10 +08:00
楼上写错了,重新写一下
------------------------------------
3 楼正解,主要发生在例如 A 、B 两个线程上锁
1. A 对某个 key 上锁成功
2. 异常原因导致 A 网络阻塞,最终 key 锁超时自动释放
3. B 成功对 key 上锁成功
4. 此时 key 锁的所有者是 B,然后 A 恢复正常,尝试对锁操作例如释放锁
5. A 对锁操作时就需要判断当前 key 锁是否还是自己持有,也就是楼主发帖所述的通过随机数匹配来判断
whoosy
2021-03-05 09:29:25 +08:00
上面已经有人说正确答案了。
简单来说 A 线程还没处理完自己的任务,锁就过期了,去释放锁的时候早都被别人抢占了,所以要加一个标识去确认是不是自己的锁,也就是上锁时传入一个自己的标识,在释放锁时判读一下是不是自己的锁,不是就跳过。
但是又引入一个新问题,判断+释放锁不是一个原子操作,又会导致释放错,所以就又引入 lua 脚本确保这俩操作原子性
dddd1919
2021-03-05 09:55:03 +08:00
首先,完整的互斥锁包括锁整个生命周期的互斥访问,A 线程创建的锁资源,B 线程不能修改和删除,甚至不能访问

再说 redis 做分布式锁,使用了 redis 核心单线程特性和特定原子方法来实现了上锁互斥,但除此以外没有任何对该锁资源的保护,实际上这个锁对于 redis 和外部应用来说也只是个普通的 key,外部应用可以随意写 key 或删 key,说白了只有你自己认为这个 key 是个锁,程序管他是什么,一个删除命令就删掉了没有任何限制

设置随机数的方式是从程序层面解决持有锁的逻辑空白,也就是为每个线程分配个 id,然后加锁时标记好持有对象,在其他任何程序主动读写锁之前都判断该资源的持有者是否是当前线程
dddd1919
2021-03-05 10:00:11 +08:00
为什么当前线程的锁会被其他线程释放? 根本原因还是 redis 的分布式锁不是一个完全的互斥锁,只能算个简陋的锁。上面回复提到的是在严谨逻辑下常出现的逻辑漏洞,不严谨的话,谁知道会不会一个手抖就给删掉了
simonlu9
2021-03-05 10:01:41 +08:00
这种释放锁之前肯定要比对当前线程 id 是否为自己才去释放的
ckdxc
2021-03-05 10:07:06 +08:00
redission 有一个 watch dog 机制, 可以自动续期 key, 避免了 A 执行时间超过设置的过期时间. 上面大家都说的很清晰了
zhangqilin
2021-03-05 13:32:53 +08:00
宣传一波自己的 redis 分布式锁的文章
https://zhuanlan.zhihu.com/p/82830007
nullpointerskz
2021-03-05 14:56:12 +08:00
这个问题,redis 的官方文档比任何出版书 /博客 都写得清楚,且这些东西大多都是搬运。建议从源头学习,先看看官网上的资料。
CantSee
2021-07-02 17:25:47 +08:00
#1 概括的很到位了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/758657

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX