您的当前位置:主页 > 微信科技 >

如何解决分布式系统中的“幽灵复现”?

时间:2020-07-21

“鬼魂复现”的问题实质归于分布式体系的“第三态”问题,即在网络体系里边,关于一个恳求都有三种回来成果:成功,失利,超时不知道。关于超时不知道,服务端对恳求指令的处理成果可所以成功或许失利,但有必要是两者中之一,不能呈现前后不共同情况。

咱们知道,当时业界有许多分布式共同性仿制协议,比方Paxos,Raft,Zab及Paxos的变种协议,被广泛用于完成高可用的数据共同性。Paxos组一般有3或5个互为冗余的节点组成,它答应在少数派节点发作停机毛病的情况下,仍然能持续供给服务,而且确保数据共同性。作为一种优化,协议一般会在节点之间推举出一个Leader专门担任建议Proposal,Leader的存在,防止了常态下并行提议的搅扰,这关于进步Proposal处理的功率有很大提高。

可是考虑在一些极点反常,比方网络阻隔,机器毛病等情况下,Leader或许会经过屡次切换和数据康复,运用Paxos协议处理日志的备份与康复时,可以确保承认构成大都派的日志不丢掉,可是无法防止一种被称为“鬼魂复现”的现象。考虑下面一种情况:

如上表所示,在榜首轮中,A成为指定Leader,宣布1-10的日志,不过后边的6-10没有构成大都派,随机宕机。随后,第二轮中,B成为指定Leader,持续宣布6-20的日志,这次,6以及20两条日志构成了大都派。随机再次发作切换,A回来了,从大都派拿到的最大LogId为20,因而决议补空泛,事实上,这次很大或许性是要从6开端,一向验证到20。咱们逐一看下会发作什么:

在上面的四类情况剖析中,1,3,4的问题不大。首要在场景2,相当于在第二轮并不存在的7~10,然后在第三列又从头呈现了。依照Oceanbase的说法,在数据库日志同步场景的情况,这个问题是不行承受的,一个简略的比如便是转账场景,用户转账时假如回来成果超时,那么往往会查询一下转账是否成功,来决议是否重试一下。假如榜首次查询转账成果时,发现未收效而重试,而转账事务日志作为鬼魂复现日志从头呈现的话,就造成了用户重复转账。

为了处理“鬼魂复现”问题,依据Multi-Paxos完成的共同性体系,可以在每条日志内容保存一个epochID,指定Proposer在生成这条日志时以当时的ProposalID作为epochID。按logID次序回放日志时,因为leader在开端服务之前必定会写一条StartWorking日志,所以假如呈现epochID相对前一条日志变小的情况,阐明这是一条“鬼魂复现”日志,要疏忽掉这条日志。

以上个比如来阐明,在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,或许由所以noop日志,可以特别化处理,即这部分日志不参加epochID的巨细比较。然后index 20日志也会被从头承认。终究,在index 21写入StartWorking日志,而且被大大都承认后,A作为leader开端接纳恳求。

首要,咱们聊一下Raft的日志康复,在 Raft 中,每次推举出来的Leader必定包括现已Committed的数据,新的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日志悉数隐式提交。

然后确保了两个作业:

针对榜首末节的场景,Raft中是不会呈现第三轮A中选leader的情况,首要关于推举,提名人比照的是终究一条日志的任期号和日志的长度。B、C的lastLogTerm和lastLogIndex都比A的lastLogTerm和lastLogIndex大,因而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日志,这儿处理了上面所说的两个问题:

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协议也就可以进入原子播送阶段。

关于第 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比自己的大,就会挑选此选票,假如持平则比较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。

在阿里云的女娲共同性体系里边,做法也是类似于Raft与Zab,确保可以制作鬼魂复现的人物无法在新的一轮推举为leader,然后防止鬼魂日志再次呈现。从服务端来看“鬼魂复现”问题,便是在failover情况下,新的leader不清楚当时的committed index,也便是分不清log entry是committed情况仍是未committed情况,所以需求经过必定的日志康复手法,确保现已提交的日志不会被丢掉,而且经过一个分界线来决议日志将会被commit仍是被drop,然后防止含糊纷歧的情况。“鬼魂复现”的问题实质归于分布式体系的“第三态”问题,即在网络体系里边, 关于一个恳求都有三种回来成果:成功,失利,超时不知道。关于超时不知道,服务端对恳求指令的处理成果可所以成功或许失利,但有必要是两者中之一,不能呈现前后不共同情况。在客户端中,恳求收到超时,那么客户端是不知道当时底层是处于什么情况的,成功或失利都不清楚,所以一般客户端的做法是重试,那么底层apply的事务逻辑需求确保幂等性,否则重试会导致数据不共同。

关于我们
我们的服务
我们的案例
新闻动态
联系我们

公司服务热线