您当前的位置:首页 > 电脑百科 > 数据库 > Redis

如何做可靠的分布式锁,Redlock真的可行么

时间:2019-08-19 12:05:40  来源:  作者:

小编认为现在 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();
 }
}

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

如何做可靠的分布式锁,Redlock真的可行么

 

上述图中,得到锁的 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 申请锁就递增一次:

如何做可靠的分布式锁,Redlock真的可行么

 

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 都获得了锁

在 Redlock 官方文档中也提到了这个情况,不过是C崩溃的时候,Redlock 官方本身也是知道 Redlock 算法不是完全可靠的,官方为了解决这种问题建议使用延时启动,相关内容可以看之前的这篇文章。但是 Martin 这里分析得更加全面,指出延时启动不也是依赖于时钟的正确性的么?

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

  1. client1 从 ABCDE 处获得了锁
  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。



Tags:分布式锁   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
概述以前参加过一个库存系统,由于其业务复杂性,搞了很多个应用来支撑。这样的话一份库存数据就有可能同时有多个应用来修改库存数据。比如说,有定时任务域xx.cron,和SystemA域...【详细内容】
2021-11-05  Tags: 分布式锁  点击:(31)  评论:(0)  加入收藏
作者 | mushishi来源 | urlify.cn/Mry6biredis分布式锁基本原理采用 redis 实现分布式锁,主要是利用其单线程命令执行的特性,一般是 setnx, 只会有一个线程会执行成功,也就是只...【详细内容】
2021-07-07  Tags: 分布式锁  点击:(105)  评论:(0)  加入收藏
前言之前的文章中通过电商场景中秒杀的例子和大家分享了单体架构中锁的使用方式,但是现在很多应用系统都是相当庞大的,很多应用系统都是微服务的架构体系,那么在这种跨jvm的场...【详细内容】
2021-01-05  Tags: 分布式锁  点击:(142)  评论:(0)  加入收藏
Nginx+Redis+MQ+DB下秒杀实现原理 Nginx+Redis+MQ+DB下限购实现原理 Nginx+Redis+MQ+DB下亿级流量实现原理 Redis在架构中的意义 分布式微服务是快了还是慢了 高可用和可用...【详细内容】
2020-12-22  Tags: 分布式锁  点击:(365)  评论:(0)  加入收藏
应用场景分布式系统中,面对高并发场景,又对数据一致性有一定要求的情况下,使用分布式锁。例如商城中下单扣库存这种情况。解决方案基于数据库例如:select * from mall_spu where...【详细内容】
2020-11-09  Tags: 分布式锁  点击:(174)  评论:(0)  加入收藏
当下在互联网技术架构中,最流行的莫过于分布式架构了。为什么大家纷纷都采用分布式架构呢? 1、高效低廉,将部署在高性能机的程序分散在多个小型机中部署;2、扩展性强,可随着业务...【详细内容】
2020-11-05  Tags: 分布式锁  点击:(96)  评论:(0)  加入收藏
分布式锁使用场景现在的系统都是集群部署,每个服务都不是单节点的了。比如库存服务,可能部署到3台机器上分别命名为节点1,节点2,节点3。库存服务需要扣减库存,扣减库存肯定需要锁...【详细内容】
2020-09-27  Tags: 分布式锁  点击:(140)  评论:(0)  加入收藏
在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。...【详细内容】
2020-09-01  Tags: 分布式锁  点击:(44)  评论:(0)  加入收藏
分布式锁通常有很多选择,基于 Redis 的,基于 Zookeeper 的,基于数据库等等方案。Redis 用于缓存数据,在项目中都有使用,所以使用 Redis 来做分布式锁的会稍微多些。如果用 Redis...【详细内容】
2020-09-01  Tags: 分布式锁  点击:(103)  评论:(0)  加入收藏
分布式锁三种实现方式:1. 基于数据库实现分布式锁;2. 基于缓存(Redis等)实现分布式锁;3. 基于Zookeeper实现分布式锁; 一, 基于数据库实现分布式锁 1. 悲观锁利用select … w...【详细内容】
2020-07-30  Tags: 分布式锁  点击:(84)  评论:(0)  加入收藏
▌简易百科推荐
来源: my.oschina.net/xiaomu0082/blog/2990388首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应...【详细内容】
2021-12-08  Java识堂    Tags:Redis   点击:(18)  评论:(0)  加入收藏
我不知道为什么你会选择对特定数量的“错误”(或警告)如此具体。听起来您正在寻找将要发布到 Yahoo! 的某些文章的内容。 Insider (N Foos to Blah for the BlahBlah)。那说:...【详细内容】
2021-12-07  富集云科技有限公司    Tags:Redis   点击:(14)  评论:(0)  加入收藏
目录 一、背景 二、步骤 0.理论支持 1、获取数据 2、结果 3、分析数据并评估大小 三、关于repl-backlog-size 一、背景 repl-backlog-size控制这个环形缓冲区. ​ 主从断...【详细内容】
2021-11-05  弈秋的美好生活    Tags:redis   点击:(41)  评论:(0)  加入收藏
Redis 性能测试是通过同时执行多个命令实现的。1,Redis-benchmarkRedis性能命令:redis性能命令格式: redis-benchmark [option] [option value] redis 性能测试工具可选参数如...【详细内容】
2021-11-02  川石信息    Tags:Redis   点击:(41)  评论:(0)  加入收藏
1 概述数据结构和内部编码 无传统关系型数据库的 Table 模型schema 所对应的db仅以编号区分。同一 db 内,key 作为顶层模型,它的值是扁平化的。即 db 就是key的命名空间。 key...【详细内容】
2021-11-01  JavaEdge    Tags:Redis   点击:(28)  评论:(0)  加入收藏
普通java中使用引用Java redis 驱动,即可连接:import redis.clients.jedis.Jedis; public class RedisTestJava { public static void main(String[] args) { //连...【详细内容】
2021-10-13  faesuite    Tags:Redis   点击:(34)  评论:(0)  加入收藏
Redis常用的数据结构有 string list set zset hashstringstring 是 Redis 的基本的数据类型,一个 key 对应一个 value。string 类型是二进制安全的,Redis的string可以包含任...【详细内容】
2021-10-12  语霖    Tags:Redis   点击:(36)  评论:(0)  加入收藏
列表类型可以存储一组按插入顺序排序的字符串,它非常灵活,支持在两端插入、弹出数据,可以充当栈和队列的角色。> LPUSH fruit apple(integer) 1> RPUSH fruit banana(integer)...【详细内容】
2021-09-17  深夜敲代码    Tags:Redis   点击:(54)  评论:(0)  加入收藏
Redis持久化意义 是做灾难恢复,数据恢复,也可以归类到高可用的一个环节里面去,比如你的redis整个挂了,然后redis就不可用了,你要做的事情是让redis变得可用,尽快变得可用 大量的请...【详细内容】
2021-08-12  小李说IT    Tags:Redis   点击:(77)  评论:(0)  加入收藏
当查询Redis中没有的数据时,该查询会下沉到数据库层,同时数据库层也没有该数据,当这种情况大量出现或被恶意攻击时,接口的访问全部透过Redis访问数据库,而数据库中也没有这些数据...【详细内容】
2021-07-30  随便t    Tags:缓存穿透   点击:(91)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条