1. 前言
开发时,碰到互斥问题,需要保证在分布式环境下,避免重复性操作修改用户状态,如:用户订单状态,购票时,修改票的余额等
2. 分布式锁的条件
分布式锁需要满足下列条件
锁需要有充足的可访问的存储空间
锁必须被唯一标识
锁至少要有两种状态
同时,要保证
安全特性:互斥访问,永远只有一个client能拿到锁
避免死锁:client最后可以拿到锁,不会出现死锁,即使原本上锁的client出现问题无法解锁
容错性:容错,只要大多数redis节点能够正常工作,客户端端都能获取和释放锁。
3. Redis单节点上锁的实现
使用下列语句获取一个不存在的key,如果可以已存在则创建失败,确保key值唯一, 加上过期时间,确保系统错误后及时解锁,避免死锁
SET key value NX PX 30000
4. Redlock算法
在分布式版本的算法里我们假设我们有N个Redis master节点,这些节点都是完全独立的,我们不用任何复制或者其他隐含的分布式协调算法。我们已经描述了如何在单节点环境下安全地获取和释放锁。因此我们理所当然地应当用这个方法在每个单节点里来获取和释放锁。在我们的例子里面我们把N设成5,这个数字是一个相对比较合理的数值,因此我们需要在不同的计算机或者虚拟机上运行5个master节点来保证他们大多数情况下都不会同时宕机。一个客户端需要做如下操作来获取锁:
- 获取当前时间(单位是毫秒)。
- 轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。
客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。 - 如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
- 如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。
5. redis分布式锁存在的问题
RedLock中,为了防止死锁,锁是具有过期时间的。所以这就会导致一定的问题:
1.持有锁的时间过长
如果 Client 1 在持有锁的时候,发生了一次很长时间的 FGC 超过了锁的过期时间。锁就被释放了。
这个时候 Client 2 又获得了一把锁,提交数据。
这个时候 Client 1 从 FGC 中苏醒过来了,又一次提交数据。
2.电脑的存在时间差
Client 1 从 A、B、C、D、E五个节点,获取了 A、B、C三个节点获取到锁,我们认为他持有了锁
这个时候,由于 B 的系统时间比别的系统走得快,B就会先于其他两个节点优先释放锁。
Clinet 2 可以从 B、D、E三个节点获取到锁。在整个分布式系统就造成 两个 Client 同时持有锁了。
解决方案:
对于第一个问题:
在一些场景中,例如http请求,可以设置请求超时时间解决这个问题。
通用解决方案是:
获取锁的时候,还需要获取一个递增的token,在上图中 Client 1 还获得了一个 token=33的 fencing。
发生了上文的 FGC 问题后,Client 获取了 token=34 的锁。
在提交数据的时候,需要判断token的大小,如果token 小于 上一次提交的 token 数据就会被拒绝。
第二点据说是所有分布式锁都存在的问题
参考:
https://www.xilidou.com/2017/10/29/Redis-RedLock-完美的分布式锁么?/
https://blog.csdn.net/Seraphimon/article/details/84944693