0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

解决分布式系统“幽灵复现”问题的四大方案

454398 来源:机器之一 作者:阿里技术 2020-10-13 16:10 次阅读

“幽灵复现”的问题本质属于分布式系统的“第三态”问题,即在网络系统里面,对于一个请求都有三种返回结果:成功,失败,超时未知。对于超时未知,服务端对请求命令的处理结果可以是成功或者失败,但必须是两者中之一,不能出现前后不一致情况。

1、“幽灵复现”问题

我们知道,当前业界有很多分布式一致性复制协议,比如Paxos,Raft,Zab及Paxos的变种协议,被广泛用于实现高可用的数据一致性。Paxos组通常有3或5个互为冗余的节点组成,它允许在少数派节点发生停机故障的情况下,依然能继续提供服务,并且保证数据一致性。作为一种优化,协议一般会在节点之间选举出一个Leader专门负责发起Proposal,Leader的存在,避免了常态下并行提议的干扰,这对于提高Proposal处理的效率有很大提升。

但是考虑在一些极端异常,比如网络隔离,机器故障等情况下,Leader可能会经过多次切换和数据恢复,使用Paxos协议处理日志的备份与恢复时,可以保证确认形成多数派的日志不丢失,但是无法避免一种被称为“幽灵复现”的现象。考虑下面一种情况:

如上表所示,在第一轮中,A成为指定Leader,发出1-10的日志,不过后面的6-10没有形成多数派,随机宕机。随后,第二轮中,B成为指定Leader,继续发出6-20的日志(B没有看到有6-10日志的存在),这次,6以及20两条日志形成了多数派。随机再次发生切换,A回来了,从多数派拿到的最大LogId为20,因此决定补空洞,事实上,这次很大可能性是要从6开始,一直验证到20。我们逐个看下会发生什么:

针对Index 6的日志,A重新走一轮basic paxos就会发现更大proposeid形成决议的6,从而放弃本地的日志6,接受已经多数派认可的日志;

针对Index 7到Index 10,因为多数派没有形成有效落盘,因此A随机以本地日志发起提议并形成多数派;

针对Index 11到Index 19,因为均没有形成有效落盘数据,因此,以noop形成补空洞;

针对Index 20,这个最简单,接受已经多数派认可的日志;

在上面的四类情况分析中,1,3,4的问题不大。主要在场景2,相当于在第二轮并不存在的7~10,然后在第三列又重新出现了。按照Oceanbase的说法,在数据库日志同步场景的情况,这个问题是不可接受的,一个简单的例子就是转账场景,用户转账时如果返回结果超时,那么往往会查询一下转账是否成功,来决定是否重试一下。如果第一次查询转账结果时,发现未生效而重试,而转账事务日志作为幽灵复现日志重新出现的话,就造成了用户重复转账。

2、基于 Multi-Paxos 解决“幽灵复现”问题

为了处理“幽灵复现”问题,基于Multi-Paxos实现的一致性系统,可以在每条日志内容保存一个epochID,指定Proposer在生成这条日志时以当前的ProposalID作为epochID。按logID顺序回放日志时,因为leader在开始服务之前一定会写一条StartWorking日志,所以如果出现epochID相对前一条日志变小的情况,说明这是一条“幽灵复现”日志,要忽略掉这条日志(说明一下,我认这里顺序是先补空洞,然后写StartWorkingID,然后提供服务)。

以上个例子来说明,在Round 3,A作为leader启动时,需要日志回放重确认,index 1~5 的日志不用说的,epochID为1,然后进入epochID为2阶段,index 6 会确认为epochID为2的StartWorking日志,然后就是index 7~10,因为这个是epochID为1的日志,比上一条日志epochID小,会被忽略掉。而Index 11~19的日志,EpochID应该是要沿袭自己作为Leader看到的上上一轮StartWorkingID(当然,ProposeID还是要维持在3的),或者因为是noop日志,可以特殊化处理,即这部分日志不参与epochID的大小比较。然后index 20日志也会被重新确认。最后,在index 21写入StartWorking日志,并且被大多数确认后,A作为leader开始接收请求。

3、基于Raft解决“幽灵复现”问题

3.1 关于Raft日志恢复

首先,我们聊一下Raft的日志恢复,在 Raft 中,每次选举出来的Leader一定包含已经Committed的数据(抽屉原理,选举出来的Leader是多数中数据最新的,一定包含已经在多数节点上Commit的数据),新的Leader将会覆盖其他节点上不一致的数据。虽然新选举出来的Leader一定包括上一个Term的Leader已经Committed的Log Entry,但是可能也包含上一个Term的Leader未Committed的Log Entry。这部分Log Entry需要转变为Committed,相对比较麻烦,需要考虑Leader多次切换且未完成Log Recovery,需要保证最终提案是一致的,确定的,不然就会产生所谓的幽灵复现问题。

因此,Raft中增加了一个约束:对于之前Term的未Committed数据,修复到多数节点,且在新的Term下至少有一条新的Log Entry被复制或修复到多数节点之后,才能认为之前未Committed的Log Entry转为Committed。

为了将上一个Term未Committed的Log Entry转为Committed,Raft 的解决方案如下:

Raft算法要求Leader当选后立即追加一条Noop的特殊内部日志,并立即同步到其它节点,实现前面未Committed日志全部隐式提交。

从而保证了两个事情:

通过最大Commit原则保证不会丢数据,即是保证所有的已经Committed的Log Entry不会丢;

保证不会读到未Committed的数据,因为只有Noop被大多数节点同意并提交了之后(这样可以连带往期日志一起同步),服务才会对外正常工作;Noop日志本身也是一个分界线,Noop之前的Log Entry被提交,之后的Log Entry将会被丢弃。

3.2 Raft解决“幽灵复现”问题

针对第一小节的场景,Raft中是不会出现第三轮A当选leader的情况,首先对于选举,候选人对比的是最后一条日志的任期号(lastLogTerm)和日志的长度(lastLogIndex)。B、C的lastLogTerm(t2)和lastLogIndex(20)都比A的lastLogTerm(t1)和lastLogIndex(10)大,因此leader只能出现在B、C之内。假设C成为leader后,Leader运行过程中会进行副本的修复,对于A来说,就是从log index为6的位置开始,C将会把自己的index为6及以后的log entry复制给A,因此A原来的index 6-10的日志删除,并保持与C一致。最后C会向follower发送noop的log entry,如果被大多数都接收提交后,才开始正常工作,因此不会出现index 7-10能读到值的情况。

这里考虑另一个更通用的幽灵复现场景。考虑存在以下日志场景:

1)Round 1,A节点为leader,Log entry 5,6内容还没有commit,A节点发生宕机。这个时候client 是查询不到 Log entry 5,6里面的内容。

2)Round 2,B成为Leader, B中Log entry 3, 4内容复制到C中, 并且在B为主的期间内没有写入任何内容。

3)Round 3,A 恢复并且B、C发生重启,A又重新选为leader, 那么Log entry 5, 6内容又被复制到B和C中,这个时候client再查询就查询到Log entry 5, 6 里面的内容了。

Raft里面加入了新Leader 必须写入一条当前Term的Log Entry 就可以解决这个问题, 其实和MultiPaxos提到的写入一个StartWorking 日志是一样的做法, 当B成为Leader后,会写入一个Term 3的noop日志,这里解决了上面所说的两个问题:

Term 3的noop日志commit前,B的index 3,4的日志内容一定会先复制到C中,实现了最大commit原则,保证不会丢数据,已经 commit 的 log entry 不会丢。

就算A节点恢复过来, 由于A的lastLogTerm比B和C都小,也无法成了Leader, 那么A中未完成的commit只是会被drop,所以后续的读也就不会读到Log Entry 5,6里面的内容。

4、基于Zab解决“幽灵复现”问题

4.1 关于Zab的日志恢复

Zab在工作时分为原子广播和崩溃恢复两个阶段,原子广播工作过程也可以类比raft提交一次事务的过程。

崩溃恢复又可以细分为Leader选举和数据同步两个阶段。

早期的Zab协议选举出来的Leader满足下面的条件:

a) 新选举的Leader节点含有本轮次所有竞选者最大的zxid,也可以简单认为Leader拥有最新数据。该保证最大程度确保Leader具有最新数据。

b) 竞选Leader过程中进行比较的zxid,是基于每个竞选者已经commited的数据生成。

zxid是64位高32位是epoch编号,每经过一次Leader选举产生一个新的leader,新的leader会将epoch号+1,低32位是消息计数器,每接收到一条消息这个值+1,新leader选举后这个值重置为0。这样设计的好处在于老的leader挂了以后重启,它不会被选举为leader,因此此时它的zxid肯定小于当前新的leader。当老的leader作为follower接入新的leader后,新的leader会让它将所有的拥有旧的epoch号的未被commit的proposal清除。

选举出leader后,进入日志恢复阶段,会根据每个Follower节点发送过来各自的zxid,决定给每个Follower发送哪些数据,让Follower去追平数据,从而满足最大commit原则,保证已commit的数据都会复制给Follower,每个Follower追平数据后均会给Leader进行ACK,当Leader收到过半Follower的ACK后,此时Leader开始工作,整个zab协议也就可以进入原子广播阶段。

4.2 Zab解决“幽灵复现”问题

对于第 1 节的场景,根据ZAB的选举阶段的机制保证,每次选举后epoch均会+1,并作为下一轮次zxid的最高32位。所以,假设Round 1阶段,A,B,C的EpochId是1,那么接下来的在Round 2阶段,EpochId为2,所有基于这个Epoch产生的zxid一定大于A上所有的zxid。于是,在Round 3,由于B, C的zxid均大于A,所以A是不会被选为Leader的。A作为Follower加入后,其上的数据会被新Leader上的数据覆盖掉。可见,对于情况一,zab是可以避免的。

对于 3.2 节的场景,在Round 2,B选为leader后,并未产生任何事务。在Round 3选举,由于A,B,C的最新日志没变,所以A的最后一条日志zxid比B和C的大,因此A会选为leader,A将数据复制给B,C后,就会出现”幽灵复现“现象的。

为了解决“幽灵复现”问题,最新Zab协议中,每次leader选举完成后,都会保存一个本地文件,用来记录当前EpochId(记为CurrentEpoch),在选举时,会先读取CurrentEpoch并加入到选票中,发送给其他候选人,候选人如果发现CurrentEpoch比自己的小,就会忽略此选票,如果发现CurrentEpoch比自己的大,就会选择此选票,如果相等则比较zxid。因此,对于此问题,Round 1中,A,B,C的CurrentEpoch为2;Round 2,A的CurrentEpoch为2,B,C的CurrentEpoch为3;Round 3,由于B,C的CurrentEpoch比A的大,所以A无法成为leader。

5、 进一步探讨

在阿里云的女娲一致性系统里面,做法也是类似于Raft与Zab,确保能够制造幽灵复现的角色无法在新的一轮选举为leader,从而避免幽灵日志再次出现。从服务端来看“幽灵复现”问题,就是在failover情况下,新的leader不清楚当前的committed index,也就是分不清log entry是committed状态还是未committed状态,所以需要通过一定的日志恢复手段,保证已经提交的日志不会被丢掉(最大 commit 原则),并且通过一个分界线(如MultiPaxos的StartWorking,Raft的noop,Zab的CurrentEpoch)来决定日志将会被commit还是被drop,从而避免模糊不一的状态。“幽灵复现”的问题本质属于分布式系统的“第三态”问题,即在网络系统里面, 对于一个请求都有三种返回结果:成功,失败,超时未知。对于超时未知,服务端对请求命令的处理结果可以是成功或者失败,但必须是两者中之一,不能出现前后不一致情况。在客户端中,请求收到超时,那么客户端是不知道当前底层是处于什么状况的,成功或失败都不清楚,所以一般客户端的做法是重试,那么底层apply的业务逻辑需要保证幂等性,不然重试会导致数据不一致。
编辑:hfy

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 网络系统
    +关注

    关注

    0

    文章

    85

    浏览量

    24970
  • 分布式系统
    +关注

    关注

    0

    文章

    146

    浏览量

    19194
  • 阿里云
    +关注

    关注

    3

    文章

    939

    浏览量

    42940
收藏 人收藏

    评论

    相关推荐

    分布式光纤测温解决方案

    分布式光纤测温解决方案
    的头像 发表于 11-12 01:02 32次阅读
    <b class='flag-5'>分布式</b>光纤测温解决<b class='flag-5'>方案</b>

    基于分布式存储系统医疗影像数据存储解决方案

    基于分布式存储系统医疗影像数据存储解决方案
    的头像 发表于 09-14 09:53 244次阅读
    基于<b class='flag-5'>分布式</b>存储<b class='flag-5'>系统</b>医疗影像数据存储解决<b class='flag-5'>方案</b>

    黑龙江电力高性能WDS分布式存储系统解决方案

    黑龙江电力高性能WDS分布式存储系统解决方案
    的头像 发表于 07-01 09:54 311次阅读
    黑龙江电力高性能WDS<b class='flag-5'>分布式</b>存储<b class='flag-5'>系统</b>解决<b class='flag-5'>方案</b>

    分布式光伏监控系统解决方案

    分布式光伏发电系统的发电量,提高分布式光伏发电系统的利用率。发展分布式光伏发电对优化能源结构、实现“双碳目标”、推动节能减排、实现经济可持续
    的头像 发表于 04-22 15:56 893次阅读
    <b class='flag-5'>分布式</b>光伏监控<b class='flag-5'>系统</b>解决<b class='flag-5'>方案</b>

    分布式光伏电力监控解决方案

    分布式光伏电力监控解决方案
    的头像 发表于 01-14 08:07 476次阅读
    <b class='flag-5'>分布式</b>光伏电力监控解决<b class='flag-5'>方案</b>

    分布式系统硬件资源池原理和接入实践

    把各个设备的硬件外设抽象为外设信息单元,外设信息在各个可信设备之间自动同步,如此,实现了外设信息的全局可见;结合分布式硬件虚拟化技术,实现任意设备之间的硬件外设能力跨设备调用;分布式硬件资源池作为系统
    发表于 12-06 10:02

    浅析Redis 分布式锁解决方案

    Redis 分布式锁解决方案是一种基于Redis实现的分布式锁机制,可以确保在分布式环境中对共享资源的访问进行同步控制,避免出现竞态条件和数据不一致的问题。在
    的头像 发表于 12-04 14:00 453次阅读

    redis分布式锁可能出现的问题及解决方案

    Redis分布式锁是一种常见的解决分布式系统中并发问题的方案。虽然Redis分布式锁具有许多优点,但也存在一些潜在的问题需要注意。本文将详细
    的头像 发表于 12-04 11:29 897次阅读

    zookeeper分布式原理

    Zookeeper是一个开源的分布式协调服务,可以用于构建高可用、高性能的分布式系统。它提供了一个简单且高效的层次命名空间,可以用来存储配置信息、状态信息、命名服务等。Zookeeper的设计目标
    的头像 发表于 12-03 16:33 601次阅读

    springcloud 分布式事务解决方案实例

    么都执行成功,要么都执行失败。本文将介绍如何使用Spring Cloud来实现分布式事务。 在分布式系统中,使用数据库事务来保证数据一致性是常见的做法。Spring Cloud通过集成各种分布
    的头像 发表于 12-03 16:32 1070次阅读

    光伏分布式监控系统方案

    光伏分布式监控系统方案具有实时监测、远程控制、可视化展示等功能,能够提高光伏电站的运行效率和管理水平,降低能耗和维护成本。虽然本方案已具备一定的优点,但仍然存在改进空间,例如可进一步优
    的头像 发表于 11-20 17:26 950次阅读

    redis分布式锁死锁处理方案

    引言: 随着分布式系统的广泛应用,尤其是在大规模并发操作下,对并发控制的需求越来越高。Redis分布式锁作为一种常见的分布式锁实现方案,由于
    的头像 发表于 11-16 11:44 1669次阅读

    什么是分布式直流电源?分布式直流电源的范围 分布式直流电的特性

    供给终端设备使用。它在分布式能源系统中起着重要的作用,具有许多独特的特性和广泛的应用范围。 首先,我们来谈谈分布式直流电源的广泛应用范围。随着可再生能源的快速发展,如太阳能光伏和风能等,分布式
    的头像 发表于 11-16 11:17 1076次阅读

    springcloud如何实现分布式

    Spring Cloud是基于Spring Boot开发的一套分布式系统解决方案,它主要包括了多个子项目,如服务注册与发现、配置中心、负载均衡、断路器、路由等等。通过使用Spring Cloud
    的头像 发表于 11-16 11:01 638次阅读

    springclould分布式教程

    Spring Cloud是一个基于Spring Boot的分布式系统开发工具,它提供了一系列的分布式系统解决方案,可以帮助开发者快速构建和部
    的头像 发表于 11-16 10:59 463次阅读