写点什么

Redisson 分布式锁源码 09:RedLock 红锁的故事

发布于: 16 小时前

前言

RedLock 红锁,是分布式锁中必须要了解的一个概念。


所以本文会先介绍什么是 RedLock,当大家对 RedLock 有一个基本的了解。然后再看 Redisson 中是如何实现 RedLock 的。


在文章开头先说明 Redisson RedLock 建议不要使用!!!在文章开头先说明 Redisson RedLock 建议不要使用!!!在文章开头先说明 Redisson RedLock 建议不要使用!!!


重要的事情重复三遍!

什么是 RedLock?

RedLock,这块可以从网上搜到很多资料,本文也简单介绍下,当做扫盲。

单实例加锁

SET resource_name my_random_value NX PX 30000
复制代码


对于单实例 Redis 只需要使用这个命令即可。


  • NX:仅在不存在 key 的时候才能被执行成功;

  • PX:失效时间,传入 30000,就是 30s 后自动释放锁;

  • my_random_value:就是随机值,可以是线程号之类的。主要是为了更安全的释放锁,释放锁的时候使用脚本告诉 Redis: 只有 key 存在并且存储的值和我指定的值一样才能删除成功


可以通过以下 Lua 脚本实现锁释放:


if redis.call("get",KEYS[1]) == ARGV[1] then    return redis.call("del",KEYS[1])else    return 0end
复制代码


为什么要设置随机值?


主要是为了防止锁被其他客户端删除。有这么一种情况:


  1. 客户端 A 获得了锁,还没有执行结束,但是锁超时自动释放了;

  2. 客户端 B 此时过来,是可以获得锁的,加锁成功;

  3. 此时,客户端 A 执行结束了,要去释放锁,如果不对比随机值,就会把客户端 B 的锁给释放了。


当然前面看过 Redisson 的处理,这个 my_random_value 存放的是 UUID:ThreadId 组合成的一个类似 931573de-903e-42fd-baa7-428ebb7eda80:1 的字符串。

当锁遇到故障转移

单实例肯定不是很可靠吧?加锁成功之后,结果 Redis 服务宕机了,这不就凉凉~


这时候会提出来将 Redis 主从部署。即使是主从,也是存在巧合的!


主从结构中存在明显的竞态:


  1. 客户端 A 从 master 获取到锁

  2. 在 master 将锁同步到 slave 之前,master 宕掉了。

  3. slave 节点被晋级为 master 节点

  4. 客户端 B 取得了同一个资源被客户端 A 已经获取到的另外一个锁。安全失效!


有时候程序就是这么巧,比如说正好一个节点挂掉的时候,多个客户端同时取到了锁。如果你可以接受这种小概率错误,那用这个基于复制的方案就完全没有问题。


那我使用集群呢?


如果还记得前面的内容,应该是知道对集群进行加锁的时候,其实是通过 CRC16 的 hash 函数来对 key 进行取模,将结果路由到预先分配过 slot 的相应节点上。


发现其实还是发到单个节点上的

RedLock 概念

这时候 Redis 作者提出了 RedLock 的概念



总结一下就是对集群的每个节点进行加锁,如果大多数(N/2+1)加锁成功了,则认为获取锁成功。

RedLock 的问题

看着 RedLock 好像是解决问题了:


  1. 客户端 A 锁住了集群的大多数(一半以上);

  2. 客户端 B 也要锁住大多数;

  3. 这里肯定会冲突,所以 客户端 B 加锁失败。


那实际解决问题了么?


推荐大家阅读两篇文章:



最终,两方各持己见,没有得出结论。


鉴于本文主要是分析 Redisson 的 RedLock,就不做额外赘述,感兴趣的小伙伴可以自己阅读。

Redisson 中 RedLock 源码

这里会简要分析一下 Redisson 中 RedLock 的源码,然后会介绍为什么文章开头不建议大家使用 Redisson 的 RedLock。

使用方式


乍一看,感觉和联锁 MultiLock 的使用方式很像啊!



实际上就是很像,RedissonRedLock 完全是 RedissonMultiLock 的子类嘛!


只不过是重写 failedLocksLimit 方法。


在 MultiLock 中,要所有的锁都锁成功才可以。


在 RedLock 中,要一半以上的锁成功。


剩余部分源码都和 MultiLock 一样,就不在重复描述了。

Redisson 中 RedLock 的问题

1、加锁 key 的问题

阅读源码之前,有一个很大的疑问,我加锁 lock1、lock2、lock3,但是 RedissonRedLock 是如何保证这三个 key 是在归属于 Redis 集群中不同的 master 呢?


因为按照 RedLock 的理论,是需要在半数以上的 master 节点加锁成功


阅读完源码之后,发现 RedissonRedLock 完全是 RedissonMultiLock 的子类,只是重写了 failedLocksLimit 方法,保证半数以上加锁成功即可。


所以这三个 key,是需要用户来保证分散在不同的节点上的。



https://github.com/redisson/redisson/issues/2436


在 Redisson 的 issues 也有同样的小伙伴提出这个问题,相关开发者给出的回复是用户来保证 key 分散在不同的 master 上。


https://github.com/redisson/redisson/issues/2127


更有小伙伴提出使用 5 个客户端。



那我使用 5 个单节点的客户端,然后再使用红锁,听着好像是可以的,并且 RedissonRedLock 可以这样使用。


但是那和 Redis 集群还有啥关系啊!


所以依然没有解决我的问题,还是需要用户自己来“手工定位锁”。


手工定位锁,这个…… 我考虑了下,还是不用 RedLock 吧!


当然 DarrenJiang1990 同学应该是怀着打破砂锅问到底的心情,又来了一篇 issue。


https://github.com/redisson/redisson/issues/2437

意思就是:不要关闭我的 issues,在 #2436 中说可以“手工定位锁”,但是我要怎么手工定位锁。


后来这个 issue 在 10 月才回复。


2、RedissonRedLock 被弃用

是的,没有看错,现在 RedissonRedLock 已经被启用了。


如果是看的英文文档,就会发现:



而中文文档,应该是没有及时更新。


来看看更新记录:



再找一找 issue:


https://github.com/redisson/redisson/issues/2669



Redisson 的开发者认为 Redis 的红锁也存在争议(前文介绍的那个争议),但是为了保证可用性,RLock 对象执行的每个 Redis 命令执行都通过 Redis 3.0 中引入的 WAIT 命令进行同步。


WAIT 命令会阻塞当前客户端,直到所有以前的写命令都成功的传输并被指定数量的副本确认。如果达到以毫秒为单位指定的超时,则即使尚未达到指定数量的副本,该命令也会返回。WAIT 命令同步复制也并不能保证强一致性,不过在主节点宕机之后,只不过会尽可能的选择最佳的副本(slaves)



源码在这一部分。



看源码,同时发送了一个 WAIT 1 1000 到 Redis。

结论

Redisson RedLock 是基于联锁 MultiLock 实现的,但是使用过程中需要自己判断 key 落在哪个节点上,对使用者不是很友好。


Redisson RedLock 已经被弃用,直接使用普通的加锁即可,会基于 wait 机制将锁同步到从节点,但是也并不能保证一致性。仅仅是最大限度的保证一致性。

相关推荐

发布于: 16 小时前阅读数: 10
用户头像

个人公众号:『 程序员小航 』 2020.07.30 加入

某不知名互联网公司 Java 程序员一枚。记录工作学习中的技术、开发及源码笔记;分享生活中的见闻感悟。

评论

发布
暂无评论
Redisson 分布式锁源码 09:RedLock 红锁的故事