一 CAP 理论
对于一个分布式系统,不能同时满足以下三点:
一致性,可用性和分区容错性
一致性:指的是分布式中数据需要保持一致。
一致性是指数据在多个副本之间是否能够保持一致的特性。当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态
可用性:指的是分布式系统中,可以对外提供服务
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果
分区容错性:即分布式系统在遇到任何网络分区或者分裂故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务
当前的网络因为区域分成2个部分,比如有5台服务器,2台在中国,3台在美国,但是中美海底电缆线断了,导致中国和美国联系不上。
一般不能同时满足:
如果发生分区,我们只能保证可用性和一致性中的一种,如果要确保数据一致性,我们必须停止服务,即牺牲可用性,即可用性不能满足;如果我们要希望继续对外提供服务,即满足可用性,但是另一部分分区的数据的一致性满足不了,所以就会牺牲一致性。
二 一致性模型
2.1 弱一致性
所谓的弱一致性,即最终一致性。DNS协议,Gossip协议就是弱一致性协议
2.2 强一致性
2.3.1 主从同步
# 主负责写数据
# 主负责将日志复制到从
# 主等待,直到所有的从返回
如果一个节点失败,Master阻塞,导致整个集群不可用,保证了一致性,但是可用性降低了。
2.3.2 多数派
基本思想:每一次都保证写入大于N/2各节点,每一次保证可以从大N/2个节点中读取。
三 Paxos算法
3.1 Paxos算法简介
Paxos算法是一个具有容错特性的一致性算法,主要的目的是解决分布式系统中,如何就某一个值(决议)达成一致的问题。
它主要涉及到以下几个角色:
# Proposer: 提议发起者。接受客户端请求,向Acceptor提出提议。允许有多个Proposer。
# Acceptor: 提议接收者。接收来自Proposer的提议,一般只有超半数的Acceptor接收就认为被接受。允许有多个Acceptor
# Learner: 提议学习者
一般在分布式系统中,每一个节点实例是一个Proposer,也是一个Acceptor,也是Learner.
另外还有一些名词:
Proposal Number: 提议的版本号,有的也叫作epoch. 代表每一轮提议的版本。
Proposal Value: 提议的值,或者说需要提交的值或者更新的值等等,即提议内容。
3.2 Basic Paxos
3.2.1 Proposer的处理
Proposer阶段一: 向Acceptor发出prepare请求
Proposer选择一个提案编号为epoch,向Acceptor集群中的成员发送请求
Proposer阶段二:根据Acceptor响应结果进行处理
#1: Proposer选定epoch,向Acceptor集群获取epoch的访问权和对应的提案值(Proposal Value)
#2 Proposer根据Acceptor集群响应结果进行不同处理
第一:如果收到超过半数Acceptor的响应,且不存在提案值,则Proposer创建<epoch,V> 提案,向Acceptor集群提交
第二:如果收到超过半数Acceptor的响应,且存在提案值V,则认同Acceptor返回的提案值V
第三:如果没有收到半数半数以上Acceptor的响应,且存在提案值,则Proposer创建<epoch,V> 提案,向Acceptor集群提交
3.2.2 Acceptor的处理
Acceptor第一阶段: 处理Proposer的prepare请求
#1: Acceptor集群向Proposer保证不再批准任何编号小于epoch的提案
#2:如果Acceptor集群已经批准过提案,那么就向Proposer返回当前已经批准的提案的值或者内容(Proposal Value);如果还没有批准过,则返回null
Acceptor第二阶段: 验证Proposer提交的提案编号epoch(Proposer Number),并更新提案值Proposal Value
#1 验证提交的提案编号是否等于现在最大的(或者最新的)提案编号epoch
#2 如果是,则设置提案值V,即<accepted_epoch,accepted_value>
3.2.3 Learner的处理
只要超半数的Acceptor批准了提案之后,就需要将提案发送给所有的Learner
3.2.4 Basic Paxos处理流程
常见的图示:
部分Acceptor节点失败,但是仍然满足大多数节点
3.2.5 Basic Paxos算法流程演示
Proposer1以prepare_epoch=1,向Acceptor集群发起Prepare请求
Acceptor集群校验lastet_epoch(最新的提案编号),如果大于lastet_epoch,则重置lastest_epoch,并且向Proposer1返回accept_value(接受的提案值)
这个时候,另外一个Proposer2也开始向Acceptor集群发起Prepare请求,请求的提案编号是epoch=2(prepare_epoch=2)
Acceptor集群校验lastet_epoch(最新的提案编号),如果大于lastet_epoch,则重置lastest_epoch,并且向Proposer1返回accept_value(接受的提案值).在这里因为prepare_epoch =2 > lastet_epoch,所以重置lastet_epoch=2
Proposer1 开始向Acceptor集群发起accept请求,提交<prepare_epoch,V1>
Acceptor集群校验lastest_epoch,发现提交的epoch=1 < lastest_epoch, 因为Proposer2提交了一个编号更大的提案。所以返回错误,表示Proposer1的提案失败
Proposer2 开始向Acceptor集群发起accept请求,提交<prepare_epoch,V2>
Acceptor集群校验lastest_epoch,发现提交的epoch=2 = lastest_epoch, 所以更新accept_epoch=2, accept_value=V2.
Proposer3向以prepare_epoch=3向Acceptor集群发起prepare请求
Acceptor集群校验lastet_epoch,因为latest_epoch < prepare_epoch,所以更新Acceptor的latest_epoch=3;并且返回当前Acceptor的accept_value,即V2
Proposer3 因为获取到了半数以上的Acceptor的响应,并且获取到了Acceptor已经接受的值V2. 此时Proposer认同Acceptor返回的已经接受的值V2
3.2.6 Basic Paxos算法优化
3.2.6.1 Learner优化
我们知道,超半数Acceptor批准提案之后,就需要将提案发送给所有的Learner,这样可以尽快让Learner获取被选定的提案,但是需要每一个Acceptor和每一个Learner通信,通信次数为:批准的Acceptor个数 * Learner个数
所以我们对此进行改进,让Learner选取一个主Learner,然后让所有批准的Acceptor提案发送给主Learner, 然后由主Learner将获取的提案,发送给其他Learner,这样Acceptor和Learner之间只需要批准的Acceptor个数次通信。但是这里主Learner随时可能发生故障
所以我们可以在Learner中选择几个作为一个集合,然后让批准的Acceptor向选定的Learner集合,集合中每一个Leaner都可以在一个提案获取之后通知其他Learner。 相当于在前面两个方案做了一个折中。
3.2.6.2 Proposer优化,解决死循环(活锁),即Multi Paxos可以解决的问题
针对在Basic Paxos,如果遇到以下场景:
Proposer1 向Acceptor集群发送prepare请求,Acceptor更改了提案编号1,并且向Proposer1返回;
Proposer1 还没有发送accept请求,Proposer2向向Acceptor集群发送prepare请求,Acceptor更改提案编号2,并且向Proposer1返回;
此时Proposer1向Acceptor集群发送accept请求,因为现在Acceptor集群中的最新的提案编号是2,大于Proposer1的提案编号1,所以Proposer1的提案没有被批准。Proposer1开始重试,选定提案编号3,然后向Acceptor集群发送prepare请求,Acceptor更改了提案编号3,并且向Proposer1返回;
此时Proposer2向Acceptor集群发送accept请求,因为现在Acceptor集群中的最新的提案编号是3,大于Proposer1的提案编号2,所以Proposer2的提案没有被批准。Proposer2开始重试,选定提案编号4,然后向Acceptor集群发送prepare请求,Acceptor更改了提案编号4,并且向Proposer1返回;
所以,循环如此,就一直处于死循环状态。
为了避免活锁的问题,我们可以只让一个Proposer进行提案,和Acceptor集群进行交互。即从多个Proposer中选择一个Proposer作为Leader,然后只有Leader Proposer才能进行提案,这样就避免了活锁的问题。
3.2.8 缺点
复杂,难以实现;需要2轮RPC,效率低;存在活锁问题。
3.3 Multi Paxos
我们知道Basic Paxos,存在一些问题,诸如复杂,难以实现;需要2轮RPC,效率低;存在活锁问题。
所以,后面就有了Multi Paxos和Fast Paxos的变异版。Fast Paxos 不坐介绍,主要特点就是需要3N+1个节点,即一个宕机,还需要三个节点可用;另外一个减少了消息交互次数,以前需要三次,现在需要2次。
3.3.1 Multi Paxos未简化版本
第一: 第一次的请求流程
# Proposer不管Client是否有请求,都会向Acceptor集群发出一个prepare请求,竞选Leader,以后由这个Leader跟Acceptor提出提案;如果Leader挂掉了,再由其他Proposer来竞选新的Leader.
# Leader Proposer来确定第一次的提案编号和提案值,然后提交交给Acceptor集群
# 如果这个提案值没有确定,则设置这个值;如果已经确定了提案值,则确认这个提案值
# Acceptor集群将提案发送给Learner
第二:后续的客户端请求流程
# 后续的客户端请求到来,直接交给Leader Proposer来处理,由Leader Proposer来处理提案的提交的顺序
# 不再需要向Acceptor集群发送prepare请求,直接确定提案编号和提案值,向Acceptor集群发送accept请求,提交提案
# 如果这个提案值没有确定,则Acceptor设置这个值;如果已经确定了提案值,则确认这个提案值
# Acceptor集群将提案发送给Learner
3.3.2 Multi Paxos简化版本
简化版本将Proposer,Acceptor和Learner
第一次请求:
# 服务器端第一次接收客户端请求,服务器会向所有服务器发送prepare请求,竞选Leader;获取大多数票数以后,则表示竞选成功
# 该服务器处理客户端的第一次请求,确定提案编号和提案值,向所有服务器提交提案
# 超半数的服务器响应成功,则提交成功
# 服务器向客户端响应结果
后续请求:
# 后续客户端请求,由Leader Server来处理,服务器不用再发送prepare请求;如果Leader 宕机,则才需要发送prepare请求选举新的Leader
# Leader服务器向所有服务器发送accept请求,提交提案编号和提案值
# 服务器开始处理,然后向客户端响应