分布式一致算法

一、拜占庭将军问题

拜占庭将军问题: 拜占庭派多支军队去围攻一个敌人,将军不确定军队中是否有叛徒,叛徒可能擅自变更进攻决定。至少一半以上的军队同时进攻才可以取胜。在这种状态下,拜占庭将军们能否找到一种分布式的协议来让他们能够远程协商,从而就进攻问题达成一致?这就是著名的拜占庭将军问题。

二、Paxos算法

背景:在常见的分布式系统中,总会发生诸如机器宕机网络异常(包括消息的延迟、丢失、重复、乱序,还有网络分区)等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。共识是在一组参与者之间就一个结果达成共识的过程。Paxos是用于解决不可靠参与者(即可能发生故障)网络中的共识的协议,这里的发生故障是指参与者节点都是无恶意节点,即会丢失、重发、不响应消息,但不会篡改消息。Paxos很好地解决了无恶意节点的分布式一致性问题。

Paxos是第一个被证明的共识算法,原理基于两阶段提交并进行扩展。每个节点在协议中可以担任多个角色。算法中将节点分为三种类型:

  • 倡议者proposer:提交一个提案,等待大家批准为结案,往往是客户端担任。
  • 接受者acceptor:负责对提案进行投票,往往服务器担任。提议超过半数的接受者投票及被选中。
  • 学习者learner:被告知提案结果,并与之统一,不参与投票过程。客户端和服务端都可担任。

Paxos的特点:

  • 一个或多个节点可以提出提议
  • 系统针对所有提案中的某个提案必须达成一致
  • 最多只能对一个确定的提案达成一致
  • 只要超过半数的节点存活且可互相通信,整个系统一定能达成一致状态。

运行过程分为两个阶段,Prepare阶段和Accept阶段。Proposer需要发出两次请求,Prepare请求和Accept请求。Acceptor根据其收集的信息,接受或者拒绝提案。

Prepare阶段

  • Proposer选择一个提案编号n,发送Prepare(n)请求给超过半数(或更多)的Acceptor。
  • 如果一个Acceptor收到一个编号为N的Prepare请求,如果N小于它已经响应过的请求,则拒绝,不回应或回复error。若N大于该Acceptor已经响应过的所有Prepare请求的编号(maxN),那么它就会将它已经接受过(已经经过第二阶段accept的提案)的编号最大的提案(如果有的话,如果还没有的accept提案的话返回{pok,null,null})作为响应反馈给Proposer,同时该Acceptor承诺不再接受任何编号小于N的提案。

Accept阶段

  • 当Proposer收到超过半数的回复时,就可以发送Accept(n, value)请求了。 n就是自己的提案编号,value是Acceptor回复的最大提案编号对应的value,如果Acceptor没有回复任何提案,value就是Proposer自己的提案内容。
  • Acceptor收到消息后,如果n大于等于之前见过的最大编号,就记录这个提案编号和内容,回复请求表示接受。
  • 当Proposer收到超过半数的回复时,说明自己的提案已经被接受。否则回到第一步重新发起提案。

三、raft算法:

背景: 和Paxos类似,Raft要解决的问题也是没有拜占庭错误下的共识问题。所谓没有拜占庭错误,简单通俗的说法就是机器节点可能会挂,网络可能延迟、重复或者丢失,但是机器节点不会作恶,传递的消息也不会被篡改。Raft算法是一种强领导者的共识算法,所谓强领导者共识算法,主要思路是在共识过程中引入一个稳定的领导者,在一段时间内,所有的值都由这个领导者来确定。Raft阶段分为两个,首先是选举过程,然后在选举出来的领导人带领进行正常操作,比如日志复制等。

角色: Raft算法是Paxos算法的一种简化实现。包括三种角色:leader,candidate和follower。

  • follower: 所有节点都以follower的状态开始,如果没有收到leader消息则会变成candidate状态, 接收leader日志。
  • candidate:会向其他节点拉选票,如果得到大部分的票则成为leader,这个过程是Leader选举。
  • leader:所有对系统的修改都会先经过leader,接受客户端的请求,并向Follower同步请求日志。

一个节点的初始身份都是Follower,其要么保持Follower,要么变为Candidate。

在这里插入图片描述

Term: Term可以认为为一个任期。Term是一个只增不减的值。每一个节点都会知道自己当前所处的Term以用来比较做出抉择。

  • 由于机器可能会宕机,因此一个Leader不可能在整个系统的生命周期都是Leader,不然如果系统能够有一个100%不故障的机器的话,那么系统也就不需要什么共识机制了,因此必然会存在运行过程中的Leader变更。而为了区分这些更迭的Leader,我们需要一个词汇来描述在不同时期时候的Leader,这个词汇的值最好还是只增不减的。
  • 当一个Leader挂掉之后,Follower变为Candidate同时希望其它节点投票给自己并化身为新一任Leader,其也需要一个来表明自己认为旧的Leader已经宕机的术语,自己需要开启新的投票。

Follower: Follower变成Candidate的条件如下。

  • 其有一段时间没能收到来自Leader或者Candidate的消息。这里需要说的是,Leader本身会维持一个心跳,以告知其它节点自己仍然存活。
  • 只要服务器从Leader或Candidate接收到有效的RPC请求,服务器就会保持Follower状态。 Leader向所有Follower发送定期心跳(不带日志条目的AppendEntries RPC)以保持其权限。 如果一个Follower在称为选举超时的一段时间内没有接到任何通信,该Follower认为没有可行的领导者并开始选举新的Leader。

Candidate: 如果一个节点身份是Candidate,其身份可能为保持为Candidate、变为Leader或者变为Follower。这取决于其面临的局面。

  • 如果其收到了当前Term或者更高Term的Leader的消息或者收到了新的Term的消息,那么其变为Follower。
  • 如果其它大多数Follower都认同其为Leader,那么其成为当前Term的Leader。
  • 如果一段时间内,上述两件事都没有发生,那么其开启一次新的选举,自己仍保持Candidate的身份。

Leader: 如果一个节点身份是Leader,其要么保持为Leader,要么变为Follower。变成Follower的条件如下。

  • 收到了更高Term的消息。

leader 选举:

  • 当服务节点启动时,先作为一个 follower。
  • 如果一台服务只要能收到从 leader 或者 candidate 的 RPC 请求, 则该台服务器保持 follower 状态。
  • leader 周期性的发送心跳到所有的 follower 以保证其权威
  • follower 在一个周期内没收到通信请求叫做 election timeout,它假定没有可以到达的 leader。开始一轮选举, follower 增加自己的任期号码,将自己转变为 candidate 状态。
  • follower 为自己投一票并且并发的向集群中其他节点发起一个 RequestVote RPC 请求
  • candidate 在以下三种情况下会变成其他状态
    • 赢得本轮选举变为 leader
    • 另外一台服务器节点成为了 leader,candidate变为follower。
    • 一个竞选周期结束之后没有选出 leader,继续进行下一次选举。
  • 一个 candidate 如果在一个任期内赢得了整个集群中大多数节点的选票,就赢得了本次选举
  • 在一轮选举中,每一台服务节点最多只会投一票(因此一轮选举最多选出一个 leader)
  • 一旦一个节点赢得选举,自己变成 leader 状态, 并向其他节点发送心跳去建立自己的权威来阻止新的选举
  • candidate 在等待选票的时候,如果收到其他声称自己是leader节点的 AppendEntries RPC请求时,AppendEntries RPC:领导人发起的一种心跳机制,复制日志也在该命令中完成。
    • 如果收到 A 的 AppendEntries 的任期号码大于或等于自己的任期号码, 那么它会承认 A 的合法性, 并将自己转化 follower 状态
    • 如果 A 的任期号比自己小, candidate 拒绝这次 RPC 调用并继续返回自己的选举
  • 如果多个 follower 同时变成了 candidate, 选票将会被多个 candidate 分享,这样可能没有一个 candidate 获得大多数的选票, 所有的 candidate 都会增加自己的任期,并开始下一轮选举。如果不采取额外措施,这种情况可能一致持续下去
  • Raft 采用随机的选举超时来确保分票这种情况很少发生,而且即使出现了也能快速被解决。

日志复制 log replication

一旦 leader 被选了出来,就开始响应客户端的请求。 每一条客户端的请求都包含一条需要被复制状态机执行的命令。

  • leader 接收客户端请求
  • leader 将命令作为一个日志条目加到它的日志中去
  • leader 并发的向其他节点发出 AppendEntries RPCs 请求
  • 当条目被安全的复制之后,leader 将条目添加到状态机中, 并向客户端返回结果

如果 follower 崩溃或者运行的很慢、或者发生了丢包, leader 不停的向 follower 发送AppendEntries RPC, 直到所有的 follower 存储了所有的 AppendEntries。

  • 每一个日志条目都有一个整数索引标记自己在日志中的位置
  • leader 决定什么时候可以安全的将 log 应用到状态机中 , 这样的 日志条目被称为 提交的日志条目
  • Raft 确保了提交的日志条目是持久化的并且最终将会被所有可用的状态己执行。
  • 一旦创建的日志条目的 leader 收到了集群中的大多数节点的确认时,该条日志条目就变成了已提交
  • 在设计 Raft 的日志机制的时候,除了简化系统的行为和让其变的可预测之外,也是确保安全性中的一个很重要的组建。 Raft 维持了以下两种特性
    • 如果不同日志中的两个条目有着相同的索引和任期号,则它们所存储的命令是相同的。
    • 如果不同日志中的两个条目有着相同的索引和任期号,则它们之前的所有条目都是完全一样的。

两个基本过程:

  • Leader选举:每个candidate随机经过一定时间都会提出选举方案,最近阶段中的票最多者被选为leader。
  • 同步log:leader会找到系统中log(各种事件的发生记录)最新的记录,并强制所有的follow来刷新到这个记录。

如何保证日志的正常复制

Leader通过强制Followers复制它的日志来处理日志的不一致,Followers上的不一致的日志会被Leader的日志覆盖。Leader为了使Followers的日志同自己的一致,Leader需要找到Followers同它的日志一致的地方,然后覆盖Followers在该位置之后的条目。

具体的操作是:Leader会从后往前试,每次AppendEntries失败后尝试前一个日志条目,直到成功找到每个Follower的日志一致位置点(基于上述的两条保证),然后向后逐条覆盖Followers在该位置之后的条目。

总结一下就是:当 leader 和 follower 日志冲突的时候,leader 将校验 follower 最后一条日志是否和 leader 匹配,如果不匹配,将递减查询,直到匹配,匹配后,删除冲突的日志。这样就实现了主从日志的一致性。

Raft一致性算法是通过选出一个leader来简化日志副本的管理,例如,,日志项(log entry)只允许从leader流向follower。

raft中的两个超时

  • 选举超时:就是新一轮选举开始时,每个节点随机思考要不要做领导者的时间,这个时间一般100-到200ms,非常短。为了防止多个节点同时发起投票,会给每个节点分配一个随机的选举超时时间(Election Timeout),即从Follower状态成为Candidate状态需要等待的时间。在这个时间内,节点必须等待,不能成为Candidate状态。主要是防止分票。Leader节点如果故障,这时候follower节点就会等待一个随机时间(选举超时),谁等待的时候更短,谁就先成为Candidate,然后向其他节点发送投票请求。
  • 心跳超时:leader节点会固定间隔时间向它的Follower节点发送心跳消息,这个固定间隔时间被称为heartbeat timeout。Follower节点收到每一条日志信息都需要向Leader节点响应这条日志复制的结果。

脑裂

  • 产生背景
  • 恢复的解决方案

读扩散和写扩散

写扩散是主动把消息写到订阅者的消息列表里,这样订阅者就不用去我的outbox拉取消息 ,所以当我要是有很多订阅者时,我就要写很多次,这就是上面定义中说的写很重。

读扩散是主动去拉取被订阅者的outbox,这样就不需要被订阅者主动写消息到我的消息列表里来,所以当我要是订阅了很多人时,我就要去读取这些人的所有新消息,所以就出现了读很重。

混合模式该方式既为读写扩散的结合,根据用户followers的数量来决定是读扩散还是写扩散。例如followers大于1k的,则使用读扩散,否则使用写扩散。

从上面的读写扩散的流程我们可以分析出读写扩散分别具有哪些优缺点。

  • 写扩散的优点:
    • 1、控制逻辑和数据读取逻辑简单
    • 2、粉丝数据独立,方便粉丝内容定制化推荐。
    • 3、大V数据丢失,对关注者数据影响不大,关注者依然可以正常读取关注者发布的数据内容。
  • 写扩散的缺点:
    • 1、浪费存储成本
    • 2、写扩散需要使用专门的扩散队列
    • 3、数据实时性较差。(要等待数据扩散完才能看到关注的内容)
  • 读扩散的优点
    • 数据实时性实时率高
    • 写入逻辑简单
    • 当读少写多时可以省下扩散成本(不需要扩散队列,也可以节省存储)
  • 读扩散的缺点
    • 数据读取会导致热点问题。

综合上面的优缺点分析,读扩散适用于在写多读少的场景,若读请求过多可能导致热点问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值