【笔记】分布式系统核心问题概述(二)

【笔记】分布式系统核心问题概述(一)

六、Paxos算法与Raft算法

  Paxos问题:指分布式的系统中存在故障(crash fault),但不存在恶意(corrupt)节点的场景(即可能消息丢失或重复,但无错误消息)下的共识达成问题。
  解决Paxos问题的算法主要有Paxos系列算法Raft算法

1.Paxos算法

  Paxos算法被广泛应用在Chubby、ZooKeeper这样的分布式系统中。

  故事背景是古希腊Paxon岛上的多个法官在一个大厅内对一个议案进行表决,如何达成统一的结果。他们之间通过服务人员来传递纸条,但法官可能离开或进入大厅,服务人员可能偷懒去睡觉。

  Paxos是第一个广泛应用的共识算法,其原理基于“两阶段提交”算法并进行泛化和扩展,通过消息传递来逐步消除系统中的不确定状态,是后来不少共识算法(如Raft、ZAB等)设计的基础。

  算法的基本原理是将节点分为三种逻辑角色,在实现上同一个节点可以担任多个角色:

  • Proposer(提案者):提出一个提案,等待大家批准(chosen)为结案(value)。系统中提案都拥有一个自增的唯一提案号。往往由客户端担任该角色;
  • Acceptor(接受者):负责对提案进行投票,接受(accept)提案。往往由服务端担任该角色;
  • Learner(学习者):获取批准结果,并可以帮忙传播,不参与投票过程。可能为客户端或服务端

  算法需要满足Safety和Liveness两方面的约束要求。

  • Safety约束:保证决议(value)结果是对的,无歧义的,不会出现错误情况。
    只有是被Proposers提出的提案才可能被最终批准;
    在一次执行中,只批准(chosen)一个最终决议。被多数接受(accept)的结果成为决议。
  • Liveness约束:保证决议过程能在有限时间内完成。
    决议总会产生,并且学习者能获得被批准的决议。

  基本过程是多个提案者先争取到提案的权利(得到大多数接受者的支持);得到提案权利的提案者发送提案给所有人进行确认,得到大部分人确认的提案成为批准的结案。

  Paxos不保证系统随时处在一致的状态。但由于每次达成一致的过程中至少有超过一半的节点参与,这样最终整个系统都会获知共识的结果。
  一个潜在的问题是Proposer在此过程中出现故障,可以通过超时机制来解决。极为凑巧的情况下,每次新一轮提案的Proposer都恰好故障,又或者两个Proposer恰好依次提出更新的提案,则导致活锁,系统永远无法达成一致(实际发生概率很小)。

  Paxos能保证在超过一半的节点正常工作时,系统总能以较大概率达成共识。

  1. 单个提案者+多接受者

  如果系统中限定只有某个特定节点是提案者,那么共识结果很容易能达成(只有一个方案,要么达成,要么失败)。提案者只要收到了来自多数接受者的投票,即可认为通过,因为系统中不存在其他的提案。
  但此时一旦提案者故障,则系统无法工作。

  1. 多个提案者+单个接受者

  限定某个节点作为接受者。这种情况下,共识也很容易达成,接受者收到多个提案,选第一个提案作为决议,发送给其他提案者即可。
  缺陷也是容易发生单点故障,包括接受者故障或首个提案者节点故障。

  1. 多个提案者+多个接受者

  一种情况是同一时间片段(如一个提案周期)内只有一个提案者,这时可以退化到单提案者的情形。需要设计一种机制来保障提案者的正确产生,例如按照时间、序列、或者大家猜拳(出一个参数来比较)之类。
  另一种情况是允许同一时间片段内可以出现多个提案者。那同一个节点可能收到多份提案,这时采用只接受第一个提案而拒绝后续提案的方法也不适用。
  提案需要带上不同的序号,节点需要根据提案序号来判断接受哪个。一种可能方案是每个节点的提案数字区间彼此隔离开,互相不冲突。为了满足递增的需求可以配合用时间戳作为前缀字段。
  同时允许多个提案意味着很可能单个提案人无法集齐足够多的投票;另一方面,提案者即便收到了多数接受者的投票,也不敢说就一定通过。因为在此过程中投票者无法获知其他投票人的结果,也无法确认提案人是否收到了自己的投票。因此,需要实现两个阶段的提交过程。

  1. 两阶段的提交

  Paxos里面对这两个阶段分别命名为准备(Prepare)阶段和提交(Commit)阶段。准备阶段通过锁来解决对哪个提案内容进行确认的问题,提交阶段解决大多数确认最终值的问题。

  • 准备阶段:

  提案者发送自己计划提交的提案的编号到多个接收者,试探是否可以锁定多数接收者的支持;
  接受者时刻保留收到过提案的最大编号和接受的最大提案。如果收到提案号比目前保留的最大提案号还大,则返回自己已接受的提案值(如果还未接受过任何提案,则为空)给提案者,更新当前最大提案号,并说明不再接受小于最大提案号的提案。

  • 提交阶段:

  提案者如果收到大多数的回复(表示大部分人听到它的请求),则可准备发出带有刚才提案号的接受消息。如果收到的回复中不带有新的提案,说明锁定成功。则使用自己的提案内容;如果返回中有提案内容,则替换提案值为返回中编号最大的提案值。如果没收到足够多的回复,则需要再次发出请求;
  接受者收到“接受消息”后,如果发现提案号不小于已接受的最大提案号,则接受该提案,并更新接受的最大提案。

  一旦多数接受者接受了共同的提案值,则形成决议,成为最终确认。

2.Raft算法

  Paxos算法的设计并没有考虑到一些优化机制,后来出现了不少性能更优化的算法和实现,包括Fast Paxos、Multi-Paxos等。最近出现的Raft算法,算是对Multi-Paxos的重新简化设计和实现,相对也更容易理解。
  Raft算法面向对多个决策达成一致的问题,分解了Leader选举、日志复制和安全方面的考虑,并通过约束减少了不确定性的状态空间。

  Raft算法包括三种角色:Leader(领导者)、Candidate(候选领导者)和Follower(跟随者),决策前通过选举一个全局的leader来简化后续的决策过程。Leader角色十分关键,决定日志(log)的提交。日志只能由Leader向Follower单向复制。

  典型的过程包括以下两个主要阶段:

  • Leader选举:开始所有节点都是Follower,在随机超时发生后未收到来自Leader或Candidate消息,则转变角色为Candidate,提出选举请求。最近选举阶段(Term)中得票超过一半者被选为Leader;如果未选出,随机超时后进入新的阶段重试。Leader负责从客户端接收log,并分发到其他节点;

  • 同步日志:Leader会找到系统中日志最新的记录,并强制所有的Follower来刷新到这个记录,数据的同步是单向的。

七、拜占庭问题与算法

  拜占庭问题(Byzantine Problem)更为广泛,讨论的是允许存在少数节点作恶(消息可能被伪造)场景下的一致性达成问题。拜占庭容错(Byzantine Fault Tolerant,BFT)算法讨论的是在拜占庭情况下对系统如何达成共识。

7.1、两将军问题

在拜占庭将军问题之前,就已经存在两将军问题(Two Generals Paradox):两个将军要通过信使来达成进攻还是撤退的约定,但信使可能迷路或被敌军阻拦(消息丢失或伪造),如何达成一致?根据FLP不可能原理,这个问题无通用解。

7.2、拜占庭问题

拜占庭问题又叫拜占庭将军问题(Byzantine Generals Problem),是Leslie Lamport等科学家于1982年提出用来解释一致性问题的一个虚构模型。拜占庭是古代东罗马帝国的首都,由于地域宽广,守卫边境的多个将军(系统中的多个节点)需要通过信使来传递消息,达成某些一致的决定。但由于将军中可能存在叛徒(系统中节点出错),这些叛徒将努力向不同的将军发送不同的消息,试图干扰共识的达成。拜占庭问题即为在此情况下,如何让忠诚的将军们能达成行动的一致。

论文中指出,对于拜占庭问题来说,假如节点总数为N,叛变将军数为F,则当N≥3F+1时,问题才有解,由BFT算法进行保证。

例如,N=3,F=1时。

提案人不是叛变者,提案人发送一个提案出来,叛变者可以宣称收到的是相反的命令。则对于第三个人(忠诚者)收到两个相反的消息,无法判断谁是叛变者,则系统无法达到一致。

提案人是叛变者,发送两个相反的提案分别给另外两人,另外两人都收到两个相反的消息,无法判断究竟谁是叛变者,则系统无法达到一致。

更一般的,当提案人不是叛变者,提案人提出提案信息1,则对于合作者来看,系统中会有N-F份确定的信息1,和F份不确定的信息(可能为0或1,假设叛变者会尽量干扰一致的达成),N-F>F,即N>2F情况下才能达成一致。

当提案人是叛变者,会尽量发送相反的提案给N-F个合作者,从收到1的合作者看来,系统中会存在(N-F)/2个信息1,以及(N-F)/2个信息0;从收到0的合作者看来,系统中会存在(N-F)/2个信息0,以及(N-F)/2个信息1;另外存在F-1个不确定的信息。合作者要想达成一致,必须进一步对所获得的消息进行判定,询问其他人某个被怀疑对象的消息值,并通过取多数来作为被怀疑者的信息值。这个过程可以进一步递归下去。

Leslie Lamport等人在论文《Reaching agreement in the presence of faults》中证明,当叛变者不超过1/3时,存在有效的拜占庭容错算法(最坏需要F+1轮交互)。反之,如果叛变者过多,超过1/3,则无法保证一定能达到一致结果。

那么,当存在多于1/3的叛变者时,有没有可能存在解决方案呢?

设想F个叛变者和L个忠诚者,叛变者故意使坏,可以给出错误的结果,也可以不响应。某个时候F个叛变者都不响应,则L个忠诚者取多数即能得到正确结果。当F个叛变者都给出一个恶意的提案,并且L个忠诚者中有F个离线时,剩下的L-F个忠诚者此时无法分别是否混入了叛变者,仍然要确保取多数能得到正确结果,因此,L-F>F,即L>2F或N-F>2F,所以系统整体规模N要大于3F。

能确保达成一致的拜占庭系统节点数至少为4,此时最多允许出现1个坏的节点。

7.3、拜占庭容错算法

拜占庭容错算法(Byzantine Fault Tolerant,BFT)是面向拜占庭问题的容错算法,解决的是在网络通信可靠但节点可能故障情况下如何达成共识。拜占庭容错算法最早的讨论在1980年Leslie Lamport等人发表的论文《Polynomial Algorithms for Byzantine Agreement》,之后出现了大量的改进工作。长期以来,拜占庭问题的解决方案都存在复杂度过高的问题,直到PBFT算法的提出。

1999年,Castro和Liskov于论文《Practical Byzantine Fault Tolerance and Proactive Recovery》中提出的Practical Byzantine Fault Tolerant(PBFT)算法,基于前人工作进行了优化,首次将拜占庭容错算法复杂度从指数级降低到了多项式级,目前已得到广泛应用。其可以在失效节点不超过总数1/3的情况下同时保证Safety和Liveness。

PBFT算法采用密码学相关技术(RSA签名算法、消息验证编码和摘要)确保消息传递过程无法被篡改和破坏。

算法的基本过程如下:

首先通过轮换或随机算法选出某个节点为主节点,此后只要主节点不切换,则称为一个视图(View);
在某个视图中,客户端将请求(REQUEST,operation,timestamp,client)发送给主节点,主节点负责广播请求到所有其他副本节点;
所有节点处理完成请求,将处理结果(REPLY,view,timestamp,client,id_node,response)返回给客户端。客户端检查是否收到了至少f+1个来自不同节点的相同结果,作为最终结果。
主节点广播过程包括三个阶段的处理:预准备(pre-prepare)阶段、准备(prepare)阶段和提交(commit)阶段。预准备和准备阶段确保在同一个视图内请求发送的顺序正确;准备和提交阶段则确保在不同视图之间的确认请求是保序的;

预准备阶段:主节点为从客户端收到的请求分配提案编号,然后发出预准备消息(PRE-PREPARE,view,n,digest,message)给各副本节点,其中message是客户端的请求消息,digest是消息的摘要;

准备阶段:副本节点收到预准备消息后,检查消息合法,如检查通过则向其他节点发送准备消息(PREPARE,view,n,digest,id),带上自己的id信息,同时接收来自其他节点的准备消息。收到准备消息的节点对消息同样进行合法性检查。验证通过则把这个准备消息写入消息日志中。集齐至少2f+1个验证过的消息才进入准备状态;

提交阶段:广播commit消息,告诉其他节点某个提案n在视图v里已经处于准备状态。如果集齐至少2f+1个验证过的commit消息,则说明提案通过。

具体实现上还包括视图切换、checkpoint机制等,读者可自行参考论文内容,在此不再赘述。

7.4、新的解决思路

拜占庭问题之所以难解,在于任何时候系统中都可能存在多个提案(因为提案成本很低),并且要完成最终一致性确认过程十分困难,容易受干扰。

比特币的区块链网络在设计时提出了创新的PoW(Proof of Work)概率算法思路,针对这两个环节进行了改进。

首先,限制一段时间内整个网络中出现提案的个数(通过增加提案成本);其次是放宽对最终一致性确认的需求,约定好大家都确认并沿着已知最长的链进行拓展。系统的最终确认是概率意义上的存在。这样,即便有人试图恶意破坏,也会付出相应的经济代价(超过整体系统一半的计算力)。

后来的各种PoX系列算法,也都是沿着这个思路进行改进,采用经济上的惩罚来制约破坏者。

八、可靠性指标

  可靠性(availability),或可用性,是描述系统可以提供服务能力的重要指标。高可靠的分布式系统往往需要各种复杂的机制来进行保障。

  通常情况下,服务的可用性可以用服务承诺(Service Level Agreement,SLA SLA)、服务指标(Service Level Indicator,SLI)、服务目标(Service Level Objective,SLO) 等方面进行衡量。

1.几个9的指标

  几个9,其实是概率意义上粗略反映了系统能提供服务的可靠性指标。
在这里插入图片描述
  单点的服务器系统至少应能满足两个9;普通企业信息系统三个9就肯定足够了,系统能达到四个9已经是领先水平了,电信级的应用一般需要能达到五个9。

2.两个核心时间

  一般地,描述系统出现故障的可能性和故障出现后的恢复能力,有两个基础的指标:MTBF和MTTR

  • MTBF(Mean Time Between Failures):平均故障间隔时间,即系统可以无故障运行的预期时间;
  • MTTR(Mean Time to Repair):平均修复时间,即发生故障后,系统可以恢复到正常运行的预期时间。

  MTBF衡量了系统发生故障的频率,而MTTR则反映了系统碰到故障后服务的恢复能力。一个高可用的系统应该是具有尽量长的MTBF和尽量短的MTTR。

3.提高可靠性

  提升系统的可靠性有两个基本思路:一是让系统中的单个组件都变得更可靠;二是干脆消灭单点。

  • 可以通过简单升级单点的软硬件来改善可靠性。
  • 消灭单点,通过主从、多活等模式让多个节点集体完成原先单点的工作。这可以从概率意义上改善服务对外的整体可靠性。
发布了123 篇原创文章 · 获赞 119 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/cbwem/article/details/104347959