本文是对 Martin Kleppmann 的文章 How to do distributed locking 部分内容的翻译和总结,上次写 Redlock 的原因就是看到了 Martin 的这篇文章,写得很好,特此翻译和总结。感兴趣的同学可以翻看原文,相信会收获良多。

  开篇作者认为现在 Redis 逐渐被使用到数据管理领域,这个领域需要更强的数据一致性和耐久性,这使得他感到担心,因为这不是 Redis 最初设计的初衷(事实上这也是很多业界程序员的误区,越来越把 Redis 当成数据库在使用),其中基于 Redis 的分布式锁就是令人担心的其一。

  Martin 指出首先你要明确你为什么使用分布式锁,为了性能还是正确性?为了帮你区分这二者,在这把锁 fail 了的时候你可以询问自己以下问题:

  1、要性能的: 拥有这把锁使得你不会重复劳动(例如一个 job 做了两次),如果这把锁 fail 了,两个节点同时做了这个 Job,那么这个 Job 增加了你的成本。

  2、要正确性的: 拥有锁可以防止并发操作污染你的系统或者数据,如果这把锁 fail 了两个节点同时操作了一份数据,结果可能是数据不一致、数据丢失、file 冲突等,会导致严重的后果。

  上述二者都是需求锁的正确场景,但是你必须清楚自己是因为什么原因需要分布式锁。

  如果你只是为了性能,那没必要用 Redlock,它成本高且复杂,你只用一个 Redis 实例也够了,最多加个从防止主挂了。当然,你使用单节点的 Redis 那么断电或者一些情况下,你会丢失锁,但是你的目的只是加速性能且断电这种事情不会经常发生,这并不是什么大问题。并且如果你使用了单节点 Redis,那么很显然你这个应用需要的锁粒度是很模糊粗糙的,也不会是什么重要的服务。

  那么是否 Redlock 对于要求正确性的场景就合适呢?Martin 列举了若干场景证明 Redlock 这种算法是不可靠的。

用锁保护资源

  这节里 Martin 先将 Redlock 放在了一边而是仅讨论总体上一个分布式锁是怎么工作的。在分布式环境下,锁比 mutex 这类复杂,因为涉及到不同节点、网络通信并且他们随时可能无征兆的 fail 。 Martin 假设了一个场景,一个 client 要修改一个文件,它先申请得到锁,然后修改文件写回,放锁。另一个 client 再申请锁 ... 代码流程如下:

// THIS CODE IS BROKEN
function writeData(filename, data) {var lock = lockService.acquireLock(filename);if (!lock) {throw 'Failed to acquire lock';}try {var file = storage.readFile(filename);var updated = updateContents(file, data);storage.writeFile(filename, updated);} finally {lock.release();}
}

  可惜即使你的锁服务非常完美,上述代码还是可能跪,下面的流程图会告诉你为什么:

  上述图中,得到锁的 client1 在持有锁的期间 pause 了一段时间,例如 GC 停顿。锁有过期时间(一般叫租约,为了防止某个 client 崩溃之后一直占有锁),但是如果 GC 停顿太长超过了锁租约时间,此时锁已经被另一个 client2 所得到,原先的 client1 还没有感知到锁过期,那么奇怪的结果就会发生,曾经 HBase 就发生过这种 Bug。即使你在 client1 写回之前检查一下锁是否过期也无助于解决这个问题,因为 GC 可能在任何时候发生,即使是你非常不便的时候(在最后的检查与写操作期间)。 如果你认为自己的程序不会有长时间的 GC 停顿,还有其他原因会导致你的进程 pause。例如进程可能读取尚未进入内存的数据,所以它得到一个 page fault 并且等待 page 被加载进缓存;还有可能你依赖于网络服务;或者其他进程占用 CPU;或者其他人意外发生 SIGSTOP 等。

  这里 Martin 又增加了一节列举各种进程 pause 的例子,为了证明上面的代码是不安全的,无论你的锁服务多完美。

使用 Fencing (栅栏)使得锁变安全

  修复问题的方法也很简单:你需要在每次写操作时加入一个 fencing token。这个场景下,fencing token 可以是一个递增的数字(lock service 可以做到),每次有 client 申请锁就递增一次:

client1申请锁同时拿到token33, 然后它进入长时间的停顿锁也过期了。client2得到锁和token34写入数据,紧接着client1活过来之后尝试写入数据,自身token33比34小因此写入操作被拒绝。注意这需要存储层来检查token,但这并不难实现。如果你使用Zookeeper作为lock service的话,那么你可以使用zxid作为递增数字。但是对于Redlock你要知道,没什么生成fencing token的方式,并且怎么修改Redlock算法使其能产生fencing token呢?好像并不那么显而易见。因为产生token需要单调递增,除非在单节点Redis上完成但是这又没有高可靠性,你好像需要引进一致性协议来让Redlock产生可靠的fencing token。

使用时间来解决一致性

  Redlock 无法产生 fencing token 早该成为在需求正确性的场景下弃用它的理由,但还有一些值得讨论的地方。

  学术界有个说法,算法对时间不做假设:因为进程可能pause一段时间、数据包可能因为网络延迟延后到达、时钟可能根本就是错的。而可靠的算法依旧要在上述假设下做正确的事情。

对于failure detector来说,timeout只能作为猜测某个节点fail的依据,因为网络延迟、本地时钟不正确等其他原因的限制。考虑到Redis使用gettimeofday,而不是单调的时钟,会受到系统时间的影响,可能会突然前进或者后退一段时间,这会导致一个key更快或更慢地过期。

可见,Redlock依赖于许多时间假设,它假设所有Redis节点都能对同一个Key在其过期前持有差不多的时间、跟过期时间相比网络延迟很小、跟过期时间相比进程pause很短。

用不可靠的时间打破 Redlock

  这节 Martin 举了个因为时间问题,Redlock 不可靠的例子。

1、client1从ABC三个节点处申请到锁,DE由于网络原因请求没有到达

2、C节点的时钟往前推了,导致lock过期

3、client2在CDE处获得了锁,AB由于网络原因请求未到达

4、此时client1和client2都获得了锁

接下来Martin又列举了进程Pause时而不是时钟不可靠时会发生的问题:

1、client1从ABCED处获得了锁

2、当获得锁的response还没到达client1时,client1进入GC停顿

3、停顿期间锁已经过期了

4、client2在ABCDE处获得了锁

5、client1 GC完成收到了获得锁的response,此时两个client又拿到了同一把锁

  同时长时间的网络延迟也有可能导致同样的问题。

Redlock 的同步性假设

  这些例子说明了,仅有在你假设了一个同步性系统模型的基础上,Redlock才能正常工作,也就是系统能满足以下属性:

1、网络延迟边界,即假设数据包一定能在某个最大延时之内达到

2、进程停顿边界,即进程停顿一定在某个最大时间之内

3、时钟错误边界,即不会从一个坏的NTP服务器处取得时间

结论

  Martin 认为 Redlock 实在不是一个好的选择,对于需求性能的分布式锁应用它太重了且成本高;对于需求正确性的应用来说它不够安全。因为它对高危的时钟或者说其他上述列举的情况进行了不可靠的假设,如果你的应用只需要高性能的分布式锁不要求多高的正确性,那么单节点Redis够了;如果你的应用想要保住正确性,那么不建议Redlock,建议使用一个合适的一致性协调系统,例如Zookeeper,且保证存在fencing token。

如何做可靠的分布式锁,Redlock真的可行么相关推荐

  1. Redis分布式锁(Redlock官方文档的理解)

    Redis分布式锁(Redlock官方文档的理解) 我github博客原文 官网解释 分布式锁在许多不同进程下需要对共享资源进行互斥操作的环境下,十分需要 Redis作者提出了 Redlock 算法 ...

  2. 精尽 Redisson 源码分析 —— 可靠分布式锁 RedLock

    1. 概述 我们来看一个 Redis 主从结构下的示例,Redis 分布式锁是如何失效的: 1.客户端 A 从 Redis Master 获得到锁 anylock . 2.在 Redis Master ...

  3. Redis构建分布式锁——Redlock

    本文来自:http://ifeve.com/redis-lock/ 简介 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁 ...

  4. 分布式Redis的分布式锁 Redlock

    引言 之前自己在用redis来实现分布式锁的时候都是基于单个Redis实例,也就是说Redis本身是有单点故障的,Redis的官方文档介绍了一种"自认为"合理的算法,Redlock ...

  5. redlock java_分布式Redis的分布式锁Redlock

    引言 之前自己在用redis来实现分布式锁的时候都是基于单个Redis实例,也就是说Redis本身是有单点故障的,Redis的官方文档介绍了一种"自认为"合理的算法,Redlock ...

  6. 基于redis集群的分布式锁redlock

    Redis 作者为了解决因为主备切换.脑裂导致 Redis 单集群分布式锁不安全的问题,提出了 redlock 算法,下面是针对 文章 的翻译和一些自我理解. 一.安全性和可用性保证 用三个属性来建模 ...

  7. 面试难题:Redis 分布式锁,真的完美无缺吗?

    点击上方"码猿技术专栏"关注,选择"设为星标" 正文-开门见山 谈起redis锁,下面三个,算是出现最多的高频词汇: setnx redLock redisso ...

  8. 分布式锁RedLock的java实现Redisson

    1. 概述 Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid).它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务.其中 ...

  9. 分布式锁redlock 之 看大佬们吹牛皮

    看大佬们吹牛皮都有意思

最新文章

  1. 2022-2028年中国汽车橡胶件行业市场调查研究及前瞻分析报告
  2. SSL协议安全系列:PKI体系中的证书吊销
  3. 理想ONE“偷袭”豪华品牌 李想强调不会收取金融服务费 | 2019上海车展
  4. 收集无良医院清单的开源项目,浏览器插件自动提示无良网站!
  5. python入门到实践-看完Python从入门到实践后该看什么书?
  6. 按要求罗列所有字符串字符序列
  7. springboot使用shiro配置多个过滤器和session同步案例
  8. Nexus搭建Maven服务器
  9. linux的基础知识——IP,UDP和TCP
  10. android 重新启动应用程序,在AsyncTask完成后重新启动完整的Android应用程序
  11. 你们网贷逾期最长多少时间,你们怎么处理的
  12. fit函数 model_函数式 API
  13. RabbitMQ之业务场景:动态创建,删除队列工具类(一)
  14. 为什么要避免使用malloc()和free()函数?
  15. [2017集训队作业自选题#149]小c的岛屿
  16. 如何改变php的语言变中文,如何使php将unicode转换中文
  17. 少儿编程--scratch编程--游来游去的鱼
  18. 像专家一样思考,像专家一样实践
  19. 连续性、间断点以及介值定理、最值定理和零点定理
  20. 2018安徽大学计算机机试2--单调栈

热门文章

  1. Web前端开发笔记——第二章 HTML语言 第十节 画布标签、音视频标签
  2. 用计算机连接路由器,用路由器怎么连接两台电脑
  3. 构造代码块、静态代码块、构造方法的执行顺序
  4. RAID简介与示例演示
  5. linux自动化安装oracle,ftp的客户端软件 Linux环境一键自动化安装oracle软件的构想(附she...
  6. FatFs最新版本获取方法
  7. cacls文件服务器备份与恢复,实战安全设置WEB专用服务器技巧
  8. python3 json解析_Python3 JSON编码解码方法详解
  9. mysql256次利用_【案例】【MySQL】一次复杂的主从库数据不一致修复
  10. java i=(int)b_定义类B和类C如下,并将其保存为B.java文件,得到的结果是()class B{int b;B(int i){b=i;}}class C extend...