分布式锁之主从架构数据同步异常问题

分布式锁之主从架构数据同步异常问题

背景

我们平常使用的分布式锁在Redis单节点情况下是可以正常使用,数据准确的。

我们会在此加上很多限制,比如避免解锁其他线程、避免服务器宕机所设置超时时间等。

而我们项目中,为了保证Redis高可用,一般都会使用「Redis Cluster」或者「哨兵模式」这两种模式。而Redis的异步数据同步就可能会造成分布式锁失效问题,本文主要讨论在分布式架构上的数据安全问题。

单节点失效原因

这边用网上的一张图来解释,为什么会锁失效?

  1. 在Master节点请求锁。
  2. Master写入成功并返回OK。
  3. Master还未同步到Slave时候挂了,Slave节点选举为Master。
  4. 锁数据丢失。

图片

Redlock算法

针对上面的问题,Redis 之父 antirez 设计了 Redlock 算法,Redlock 的算法描述就放在 Redis 的官网上:

  • https://redis.io/topics/distlock

在 Redlock 之前,很多人对于分布式锁的实现都是基于单个 Redis 节点的。而 Redlock 是基于多个 Redis 节点(都是 Master)的一种实现。前面基于单 Redis 节点的算法是 Redlock 的基础。

1.什么是Redlock?

Redlock 算法基于 N 个完全独立的 Redis 节点,客户端依次执行下面各个步骤,来完成获取锁的操作:

  1. 获取当前时间 T1(毫秒数)。

  2. 向所有Redis节点依次加锁。

    (使用相同的 key、value 按顺序依次向 N 个 Redis 节点执行获取锁的操作。这个获取操作跟前面基于单 Redis 节点的获取锁的过程相同,包含随机字符串 my_random_value,也包含过期时间(比如 PX 30000,即锁的有效时间)。为了保证在某个 Redis 节点不可用的时候算法能够继续运行,这个获取锁的操作还有一个超时时间(time out),它要远小于锁的有效时间(几十毫秒量级)。客户端在向某个 Redis 节点获取锁失败以后,应该立即尝试下一个 Redis 节点。)

  3. 多半节点成功获取锁,且加锁时间不超过锁的有效时间。

    获取当前时间 T2 减去步骤 1 中的 T1,计算获取锁消耗了多长时间(T3= T2-T1),计算方法是用当前时间减去第 1 步记录的时间。如果客户端从大多数 Redis 节点(大于等于 N/2+1)成功获取到了锁,并且获取锁总共消耗的时间没有超过锁的有效时间(lock validity time),那么这时客户端才认为最终获取锁成功;否则,认为最终获取锁失败。

  4. 锁的有效时间要减去加锁花费时间

    如果最终获取锁成功了,那么这个锁的有效时间应该重新计算,它等于最初的锁的有效时间减去第 3 步计算出来的获取锁消耗的时间

  5. 加锁失败需要将加锁成功的节点解锁

    如果最终获取锁失败了(可能由于获取到锁的 Redis 节点个数少于 N/2+1,或者整个获取锁的过程消耗的时间超过了锁的最初有效时间),那么客户端应该立即向所有 Redis 节点发起释放锁的操作

注意:⚠️我们平时操作Redis是对主节点进行增删,但是我们加锁是直接和集群各个节点通信获取锁。

2.Redlock为什么要这样做

问题 1:为什么要在多个实例上加锁?

本质就是保证分布式锁系统高可用,单节点有可能崩溃后数据丢失。而多实例加锁,能够最大限度避免Redis服务数据丢失问题,部分实例异常宕机,剩余实例只要超过 N/2+1 依旧可用。

问题 2:为什么步骤 3 加锁成功之后,还要计算加锁的累计耗时?

因为是对所有节点加锁,其中需要每个节点的网络来回,请求多延迟、丢包概率就高。如果累计耗时超过锁的存在时间,那么此时锁没有任何意义了,相当于已经解锁了。

问题 3:为什么释放锁,要操作所有节点,对所有节点都释放锁?

因为当对某一个 Redis 节点加锁时,可能因为网络原因导致加锁“失败”。注意这个“失败”,指的是 Redis 节点实际已经加锁成功了,但是返回的结果因为网络延迟并没有传到加锁的线程,被加锁线程丢弃了,加锁线程误以为没有成功,于是加锁线程去尝试下一个节点了。

所以释放锁的时候,不管以前有没有加锁成功,都要释放所有节点的锁,以保证清除节点残留的锁。

3.Redlock就完美解决了吗?

1.服务器崩溃

考虑一种情况,如果我们刚好只对其中“大多数”加锁成功了。

如下图:Redis1、Redis2、Redis3成功了

图片

这个时候Redis3宕机了,刚好发生了单节点数据丢失的问题了,恢复后发现Redis3、Redis4、Redis5被其他线程加锁成功了。锁失效了。

如果配置了AOF持久化策略采用了fsync=always,保存重启后不会丢失,但这样性能大大降低,不符合高性能的标准。

-我们可以把重启时间人为控制,超过锁的最久超时时间即可,只要锁都超时,就能保证没有同时有两个线程加锁成功情况了。

2.客户端长期阻塞导致锁过期

我们来看一张图,在线程1获取到锁后发生了很长时间的STW,这个时候锁自动过时了,被线程2获取了,那么锁过期了。

图片

解决方案——fencing token

fencing token 是一个**单调递增的数字,**当客户端成功获取锁的时候它随同锁一起返回给客户端。而客户端访问共享资源的时候带着这个 fencing token,这样提供共享资源的服务就能根据它进行检查,拒绝掉延迟到来的访问请求(避免了冲突)。

图片

在上图中,客户端 1 先获取到的锁,因此有一个较小的 fencing token,等于 33,而客户端 2 后获取到的锁,有一个较大的 fencing token,等于 34。客户端 1 从 GC pause 中恢复过来之后,依然是向存储服务发送访问请求,但是带了 fencing token = 33。存储服务发现它之前已经处理过 34 的请求,所以会拒绝掉这次 33 的请求。这样就避免了冲突。

3.时间跳跃

构造了一些事件序列,能够让 Redlock 失效(两个客户端同时持有锁)。为了说明 Redlock 对系统记时(timing)的过分依赖,首先给出了下面的一个例子(还是假设有 5 个 Redis 节点 A, B, C, D, E):

  1. 客户端 1 从 Redis 节点 A, B, C 成功获取了锁(多数节点)。由于网络问题,与 D 和 E 通信失败。
  2. 节点 C 上的时钟发生了向前跳跃(比如人为修改时间),导致它上面维护的锁快速过期。
  3. 客户端 2 从 Redis 节点 C, D, E 成功获取了同一个资源的锁(多数节点)。
  4. 客户端 1 和客户端 2 现在都认为自己持有了锁。

本质上是因为Redlock 的安全性(safety property)对系统的时钟有比较强的依赖,一旦系统的时钟变得不准确

一个好的分布式算法,这些因素不应该影响它的安全性(safety property),只可能影响到它的活性(liveness property)

也就是说,即使在非常极端的情况下(比如系统时钟严重错误),算法顶多是不能在**有限的时间内给出结果而已,而不应该给出错误的结果。这样的算法在现实中是存在的,像比较著名的Paxos,或 Raft。**但显然按这个标准的话,Redlock 的安全性级别是达不到的。

3.1 时钟变迁如何解决

为什么系统时钟会存在迁移

linux 提供了两个系统时间:clock realtime 和 clock monotonic

  • clock realtime 也就是 xtime/wall time,这个时间是可以被用户改变的,被 NTP 改变。Redis 的判断超时使用的 gettimeofday 函数取的就是这个时间,Redis 的过期计算用的也是这个时间。

    参考https://blog.habets.se/2010/09/gettimeofday-should-never-be-used-to-measure-time.html

  • clock monotonic,直译过来是单调时间,不会被用户改变,但是会被 NTP 改变。

clock realtime 可以被人为修改,在实现分布式锁时,不应该使用 clock realtime。不过很可惜,Redis 使用的就是这个时间,Redis 5.0 使用的还是 clock realtime。Redis作者说过后面会改成 clock monotonic 的。也就是说,人为修改 Redis 服务器的时间,就能让 Redis 出问题了。

发生时钟迁移情况

  • 人为修改了时钟
  • 从 NTP 服务收到了一个大的时钟更新事件导致时钟漂移
  • 闰秒(是指为保持协调世界时接近于世界时时刻,由国际计量局统一规定在年底或年中或者季末对协调世界时增加或减少 1 秒的调整,此时一分钟为 59 秒或者 61 秒,闰秒曾使许多大型系统崩溃)

如何解决

  1. Fencing token 机制:类似 raft 算法、zab 协议中的全局递增数字,对这个 token 的校验需要后端资源进行校验,如此一来,相当于后端资源具备了互斥机制,而且涉及到后端资源的改造。

总结

Redlock在Redis集群可以解决大部分发生的,数据不安全的问题,但并不是完全解决,因为在极端情况下还是会出现服务器时间过期等情况。其核心的问题为:缺乏锁数据丢失的识别和感知机制。

RedLock 中的每台 Redis,充当的仍旧只是分布式存储锁数据的功能,每台 Redis 之间各自独立,单台 Redis 缺乏全局的信息,自然也不知道自己的锁数据是否是完整的。在单台 Redis 数据的不完整的前提下,没有分布式共识机制, 使得在各种分布式环境的典型场景下(结点故障、网络丢包、网络乱序),没有完整数据但参与决策,从而破坏数据一致性。

转载自: https://mp.weixin.qq.com/s/-N4x6EkxwAYDGdJhwvmZLw

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
上百节课详细讲解,需要的小伙伴自行百度网盘下载,链接见附件,永久有效。 课程介绍: 01_先来看一个互联网java工程师的招聘JD 02_互联网Java工程师面试突击训练课程第一季的内容说明 03_关于互联网Java工程师面试突击训练课程的几点说明 04_体验一下面试官对于消息队列的7个连环炮 05_知其然而知其所以然:如何进行消息队列的技术选型? 06_引入消息队列之后该如何保证其高可用性? 07_我的天!我为什么在消息队列里消费到了重复的数据? 08_啥?我发到消息队列里面的数据怎么不见了? 09_我该怎么保证从消息队列里拿到的数据按顺序执行? 10_完了!生产事故!几百万消息在消息队列里积压了几个小时! 11_如果让你来开发一个消息队列中间件,你会怎么设计架构? 12_总结一下消息队列相关问题的面试技巧 13_体验一下面试官对于分布式搜索引擎的4个连环炮 14_分布式搜索引擎的架构是怎么设计的?为啥是分布式的? 15_分布式搜索引擎写入和查询的工作流程是什么样的? 16_分布式搜索引擎在几十亿数据量级的场景下如何优化查询性能? 17_你们公司生产环境的分布式搜索引擎是怎么部署的呢? 18_总结一下分布式搜索引擎相关问题的面试技巧 19_先平易近人的随口问你一句分布式缓存的第一个问题 20_来聊聊redis的线程模型吧?为啥单线程还能有很高的效率? 21_redis都有哪些数据类型?分别在哪些场景下使用比较合适呢? 22_redis的过期策略能介绍一下?要不你再手写一个LRU? 23_怎么保证redis是高并发以及高可用的? 24_怎么保证redis挂掉之后再重启数据可以进行恢复? 25_你能聊聊redis cluster集群模式的原理吗? 26_你能说说我们一般如何应对缓存雪崩以及穿透问题吗? 27_如何保证缓存与数据库双写时的数据一致性? 28_你能说说redis的并发竞争问题该如何解决吗? 29_你们公司生产环境的redis集群的部署架构是什么样的? 30_分布式缓存相关面试题的回答技巧总结 31_体验一下面试官可能会对分布式系统发起的一串连环炮 32_为什么要把系统拆分成分布式的?为啥要用dubbo? 33_dubbo的工作原理是啥?注册中心挂了可以继续通信吗? 34_dubbo都支持哪些通信协议以及序列化协议? 35_dubbo支持哪些负载均衡、高可用以及动态代理的策略? 36_SPI是啥思想?dubbo的SPI机制是怎么玩儿的? 37_基于dubbo如何做服务治理、服务降级以及重试? 38_分布式系统中接口的幂等性该如何保证?比如不能重复扣款? 39_分布式系统中的接口调用如何保证顺序性? 40_如何设计一个类似dubbo的rpc框架?架构上该如何考虑? 41_说说zookeeper一般都有哪些使用场景? 42_分布式是啥?对比下redis和zk两种分布式的优劣? 43_说说你们的分布式session方案是啥?怎么做的? 44_了解分布式事务方案吗?你们都咋做的?有啥坑? 45_说说一般如何设计一个高并发的系统架构? 46_体验一下面试官对于分库分表这个事儿的一个连环炮 47_来来来!咱们聊一下你们公司是怎么玩儿分库分表的? 48_你们当时是如何把系统不停机迁移到分库分表的? 49_好啊!那如何设计可以动态扩容缩容的分库分表方案? 50_一个关键的问题!分库分表之后全局id咋生成? 51_说说MySQL读写分离的原理?主从同步延时咋解决? 52_如何设计高可用系统架构?限流?熔断?降级?什么鬼!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值