拜占庭将军问题
信道不安全情况下,怎么实现数据的一直性
维基百科解释:在分布式计算中,不同的计算机通过交换信息达成共识进行协调工作,但有时候,系统成员计算机可能发出错误的信息,或者用于传递信息的通讯网络信息损坏,是的网络中不同的成员得出最终不同的协作指令,从而破坏一直性。
问题描述:
一组拜占庭将军分别各率领一支军队共同围困一座城市。为了简化问题,将各支军队的行动策略限定为进攻或撤离两种。因为部分军队进攻部分军队撤离可能会造成灾难性后果,因此各位将军必须通过投票来达成一致策略,即所有军队一起进攻或所有军队一起撤离。因为各位将军分处城市不同方向,他们只能通过信使互相联系。在投票过程中每位将军都将自己投票给进攻还是撤退的信息通过信使分别通知其他所有将军,这样一来每位将军根据自己的投票和其他所有将军送来的信息就可以知道共同的投票结果而决定行动策略。
存在的问题:
如果这一组将军中存在一个叛徒,则会误导其他忠诚将军的判断,最终导致失败;或者将军相互之间投票结果被敌人截获,变更错误的信息发送,则也可能导致将军们收到的决策不一致,导致进攻或者撤退的任务失败。这种存在的问题就是拜占庭问题
解决:
假设哪些忠诚的将军可以通过多数(过半)决定来决定他们的战略,这就是拜占庭容错。
方案一:如果叛军的人数是m,将军人数不能少于3m+1
方案二:签名消息解决拜占庭问题 : 通过加密验签,将军之间相互通讯每位将军获取到2n-1个指令,如果收到命令很多按照默认的值取,比如说按照字典顺序排序取n/2的位置,这样可以保证忠诚将军行动一致性 n位将军,能容忍(n - 2) 位叛将
计算机系统
在分布式对等网络中需要按照共同一致策略协作的成员计算机即为问题中的将军,而各成员计算机赖以进行通讯的网络链路即为信使。拜占庭将军问题描述的就是某些成员计算机或网络链路出现错误、甚至被蓄意破坏者控制的情况。
拜占庭容错算法: 拜占庭将军问题描述的是最困难的,也是最负载的一种分布式,除了存在故障信息,还存在恶意行为的场景。一般用于数字的区块链技术中,常见的算法:PBFT算法、Pow算法
非拜占庭容错算法:CFT(Crash Fault Tolenrance),不存在恶意节点(假设没有将军叛变、信道也是安全的)的共识问题,分布式系统中故障的解决。常见的算法:Paxos算法、Raft算法、ZAB协议
CAP 理论
一致性、可以性、分区容错性。在分布式系统中只能保证两个,由于如果网络分区,那也就不算分布式系统所以P必须存在,也就是在设置分布式系统中只能在A和C之间做取舍
有网络交互就有数据的延迟和丢失,我们必须接受,还得保证系统不能挂了,节点间的分区故障必然会发生,也就是说,分区容错(P)是前提,必须要保证的。作为分布式系统,分区容错是必须要实现的,不能因为节点间出现分区故障,而整个系统不工作的情况。
选择了CP 一定会读到最新的数据,但是会增加网络延迟
选择AP 某些节点可能无法返回最新的数据
ACID
分布式事务协议:两阶段提交、TCC
两阶段提交:第一个阶段准备资源、准备好事务;第二个阶段执行事务或者放弃事务
TCC:TCC不依赖于数据库,而是在业务中实现分布式事务,这样能减轻数据库的压力,但是业务代码的侵入性更强,实现的复杂度更高。
在操作的过程中一定要保证幂等性,唯一标识,可以通过token和索引来标识
BASE理论
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性),base理论是对cap中ap的一个扩展,通过牺牲一致性来获取可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内不一致,但要达到最终一致性。 base理论是对cap中一致性和可用性的权衡结果其来源于大规模互联网实践的总结,是基于cap定理的逐步演化,base理论的核心思想:既然无法做到强一致性,那么每个业务采用自身的方式执行业务,在合适的时候使系统达到最终一致性。这也叫柔性事务。
实现最终一致性的几个方案:
-
读时修复:在读取数据时,检测数据的不一致,进行修复。比如Cassandra 的Read Repair实现,具体来说,在向Cassandra系统查询数据的时候,如果检测到不同节点的副本数据不一致,系统就自动修复数据。
-
写时修复:在写入数据,检测数据的不一致时,进行修复。比如Cassandra 的Hinted Handoff实现。具体来说,Cassandra集群的节点之间远程写数据的时候,如果写失败就将数据缓存下来,然后定时重传,修复数据的不一致性。
-
异步修复:这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复(实现自定义写一致性级别 ,比如All、Quorum、One、Any)
Basic Paxos
重点:
-
Basic Paxos是通过二阶段提交的方式来达成共识的。
-
除了共识,Basic Paxos还实现了容错,在少于一半的节点出现故障时,集群也能工作。
-
本质上而言,提案编号的大小代表着优先级,你可以这么理解,根据提案编号的大小,接 受者保证三个承诺,具体来说:
-
如果准备(prepare)请求的提案编号,小于等于接受者已经响应的准备请求的提案编号,那么接受者将承诺不响应这个准备请求;
-
如果接受(accept)请求中的提案的提 案编号,小于接受者已经响应的准备请求的提案编号,那么接受者将承诺不通过这个提 案;
-
如果接受者之前有通过提案,那么接受者将承诺,会在准备请求的响应中,包含已经 通过的最大编号的提案信息。
-
问题:
-
如果多个提议者同时提交提案,可能会出现提案编号冲突,在准备阶段没有提议者的提议超过半数,协商失败,需要重新协商,可能导致活锁问题
-
2轮的RPC通讯往返消息多,耗性能,延迟大。
算法描述
-
三种角色: 在 Paxos 算法中有三种角色,分别具有三种不同的行为。但很多时候,一个进程可能同 时充当着多种角色。
-
Proposer:提案者
-
Acceptor:表决者
-
Learner:同步者-数据的备份节点,接受共识
-
Paxos 算法的一致性: paxos 算法的一致性主要体现在以下几点:
-
每个提案者在提出提案时都会首先获取到一个具有全局唯一性的、递增的提案编号 N(这里的n针对的是所有事件,不是某个事件),即在整个集群中是唯一的编号 N,然后将该编号赋予其要提出的提案。
-
每个表决者在 accept 某提案后,会将该提案的编号 N 记录在本地,这样每个表决者中 保存的已经被 accept 的提案中会存在一个编号最大的提案,其编号假设为 maxN。每个 表决者仅会 accept 编号大于自己本地 maxN 的提案。
-
在众多提案中最终只能有一个提案被选定。
-
一旦一个提案被选定,则其它服务器会主动同步(Learn)该提案到本地。
-
没有提案被提出则不会有提案被选定。
-
算法描述过程 Paxos 算法的执行过程划分为两个阶段:准备阶段 prepare 与接受阶段 accept。
A 、 prepare 阶段
-
提案者(Proposer)准备提交一个编号为 N 的提议,于是其首先向所有表决者(Acceptor)发 送 prepare(N)请求,用于试探集群是否支持该编号的提议。
-
每个表决者(Acceptor)中都保存着自己曾经 accept 过的提议中的最大编号 maxN。当一个 表决者接收到其它主机发送来的 prepare(N)请求时,其会比较 N 与 maxN 的值。有以下 几种情况:
-
若 N 小于 maxN,则说明该提议已过时,当前表决者采取不回应或回应 Error 的方 式来拒绝该 prepare 请求;
-
若N大于maxN,则说明该提议是可以接受的,当前表决者会首先将该N记录下来, 并将其曾经已经 accept 的编号最大的提案 Proposal(myid,maxN,value)反馈(这里反馈就是表示同意 与value值无关)给提案者, 以向提案者展示自己支持的提案意愿。其中第一个参数 myid 表示该提案的提案者 标识 id,第二个参数表示其曾接受的提案的最大编号 maxN,第三个参数表示该提 案的真正内容 value。当然,若当前表决者还未曾 accept 过任何提议,则会将 Proposal(myid,null,null)反馈给提案者。
-
在 prepare 阶段 N 不可能等于 maxN。这是由 N 的生成机制决定的。要获得 N 的值, 其必定会在原来数值的基础上采用同步锁方式增一。
B 、 accept
-
当提案者(Proposer)发出 prepare(N)后,若收到了超过半数的表决者(Accepter)的反馈, 那么该提案者就会将其真正的提案 Proposal(myid,N,value)发送给所有的表决者。
-
当表决者(Acceptor)接收到提案者发送的Proposal(myid,N,value)提案后,会再次拿出自己曾经 accept 过的提议中的最大编号 maxN,或曾经记录下的 prepare 的最大编号,让 N 与它们进行比较,若 N 大于等于这两个编号,则当前表决者 accept 该提案,并反馈给提案者。若 N 小于这两个编号,则表决者采取不回应或回应 Error 的方式来拒绝该提议。
-
若提案者没有接收到超过半数的表决者的 accept 反馈,则有两种可能的结果产生。一 是放弃该提案,不再提出;二是重新进入 prepare 阶段,递增提案号,重新提出 prepare 请求。
-
若提案者接收到的反馈数量超过了半数,则其会向外广播两类信息:
-
向曾 accept 其提案的表决者发送“可执行数据同步信号”,即让它们执行其曾接收 到的提案;
-
向未曾向其发送 accept 反馈的表决者发送“提案 + 可执行数据同步信号”,即让 它们接受到该提案后马上执行。
准备---通过--执行
Paxos 算法的活锁问题
前面所述的Paxos算法在实际工程应用过程中,根据不同的实际需求存在诸多不便之处, 所以也就出现了很多对于基本 Paxos 算法的优化算法,以对 Paxos 算法进行改进,例如,MultiPaxos、Fast Paxos、EPaxos。 例如,Paxos 算法存在“活锁问题”,Fast Paxos 算法对 Paxos 算法进行了改进:只允许 一个进程提交提案,即该进程具有对N的唯一操作权。该方式解决了“活锁(一直的死循环,条件一直成立出不来)”问题。
Multi-Paxos
Multi-Paxos是一种思想,通过多个Basic Paxos实现的一系列共识算法的统称(Raft等)
Leader
唯一个提议者,这样就不存在多个提议者的活锁问题,提议冲突问题,如何选举leader,需要我们按照自己的需要实现,
优化两阶段执行
当领导者稳定之后,直接进入接受阶段,省掉了准备阶段,接受节点只需要执行leader发送的数据执行
Fast Paxos
如果Leader检测到冲突会发起一轮新的投票,并发送给accept消息向接收方,重新进行投票leader选举;
如果Leader提前指定了一种冲突恢复机制,就可以实现另外一种优化,它允许Acceptors自己执行冲突恢复。
其实这个方式的实现可以看一下zk的leader选举机制
ZAB协议
能够保证顺序性的,基于主备模式的原子广播协议。
所有的写操作必须在主节点上,主节点上所有的操作是顺序性的
当主节点接收到写请求后,它会基于写请求中的指令(也就是X,Y),来创建一个提 案(Proposal),并使用一个唯一的ID来标识这个提案。这里我说的唯一的ID就是指事务标 识符(Transaction ID,也就是zxid),就像下图的样子。
从图中你可以看到,X、Y对应的事务标识符分别为<1, 1>和<1, 2>,这两个标识符是什么含义 呢?你可以这么理解,事务标识符是64位的long型变量,有任期编号epoch和计数器counter两部 分组成(为了形象和方便理解,我把epoch翻译成任期编号),格式为<epoch, counter>, 高32位为任期编号,低32位为计数器:
任期编号,就是创建提案时领导者的任期编号,需要你注意的是,当新领导者当选时,任
期编号递增,计数器被设置为零。比如,前领导者的任期编号为1,那么新领导者对应的任 期编号将为2。
计数器,就是具体标识提案的整数,需要你注意的是,每次领导者创建新的提案时,计数 器将递增。比如,前一个提案对应的计数器值为1,那么新的提案对应的计数器值将为2。
为什么要设计的这么复杂呢?因为事务标识符必须按照顺序、唯一标识一个提案,也就是
说,事务标识符必须是唯一的、递增的。
在创建完提案之后,主节点会基于TCP协议,并按照顺序将提案广播到其他节点。这样就能
保证先发送的消息,会先被收到,保证了消息接收的顺序性。