1.分布式系统
在介绍zk之前,先说下分布式系统,看看为什么需要用zk。分布式系统是系统的组件分布在不同的网络环境或者不同的计算机上,彼此之间通过消息传递进行通信或者协调。
1.1 分布式系统特点
(1)对等性
对等性也可以认为是数据冗余,就是为了防止单节点故障导致服务不可用,每个服务都会做主备或者集群。
(2)并发性
这里的并发不是说多线程里面的并发,是多进程、多JVM的,这时候如果操作一个资源,也会有多线程的锁问题。
(3)全局时钟
每个组件在不同的环境,都有自己的时间,很难定义事务的先后顺序。
(4)通信故障
在分布式中各组件需要通信,那因为网络故障,通信的延迟或者信息丢失都有可能发生。而且因为主备中的选举,因为网络问题导致脑裂。通信故障还有可能带来一个问题,事件除了成功、失败、还有第三种状态:超时态。因为通信故障不知道有没有成功。
2.分布式事务
(1)CAID
我们常用的mysql数据库的事务都是强一致性,也就是常说的CAID.
- Atomicity(原子性):事务要么全部成功、要么全部失败
- Consistency(一致性):要么看到事务之前的状态、要么看到事务之后的状态,不可能看到中间状态
- Isolation(隔离性):各事务之间互不影响
- Durtability(持久性):数据成功后,数据持久保存在数据库中
(2)CAP
- Consistency(一致性):分布式环境数据有多个副本,这些副本可能在一个网络可以在不同网络环境。这里的一致性还是说强一致性,一个数据的变化,数据的副本可以立即发生变化,保持一致。
- Availability(可用性):分布式系统一致处于一种可用状态,可用状态是在有效时间得到有效结果
- Partition tolerance(分区容错性):只要网络不是全部瘫痪,系统对外要保持一致性和可用性
显然在分布式环境中很难做到CAP,在不同网络下无法一定保证强一致性。单节点又很难保证可用性。所以我们一般是放弃掉一项,显然不可能放弃分布式环境,只能在一致性和可用性之间找平衡。
(3)BASE
base理论是在CAP之上发展的,分布式中不可能做到强一致性,系统不可用肯定也是不允许的。 - Basically Avaliable(基本可用):系统可以在时间上有稍微的延迟、主功能可用,有部分功能不可用
- Soft state(软状态):系统可以存在中间状态,因为数据同步有延迟
- Eventually consistent(最终一致性):数据可以暂时不一致,但最终必须一致
3. 分布式算法
3.1 2pc
2pc即两阶段提交,2pc将事务分为两个阶段:准备阶段和提交阶段。这里有个事务的全局管理者称为协调者,负责协调参与事务提交的各节点。
- 准备阶段:协调者向所有参数与者发送事务提交请求,询问是否可以提交。各事务参与者执行事务,将undo和redo写入日志文件,但不提交。执行成功返回可以提交,执行失败返回不可以提交。
- 提交阶段:事务协调者收到各事务的回复,向各事务发出提交或者回滚通知。各事务提交或者回滚事务,然后释放对象锁。向事务协调者发送完成信号,协调者确认已完成。
2pc的缺点:必须要使用支持XA协议的datasource数据源;要同时锁定多个数据库,事务锁定时间大大延长 ,性能不好;而且容易出现因为网络问题导致协调者没有收到一个事务的消息,导致另外一个事务一致等待。
3.2 3pc
针对2pc的超时和阻塞问题,对2pc进行了优化。3pc分为三个阶段:CanCommit、PreCommit、do Commit。
- CanCommit:协调者向各事务发送CanCommit请求,各事务回复是否可以提交。各事务收到CanCommit请求后,如果认为可以执行事务操作,则反馈YES并进入预备状态,否则反馈NO。
- PreCommit:各事务参与者执行事务或者中断事务,如果执行事务,将undo和redo写入日志文件,但不提交。并返回协调者,等待最终指令。这个阶段如果协调者没有收到各事务回应、或者各事务没有收到协调者的指令,事务将自行中断事务。
- do Commit:协调者如果收到各事务的确认可提交指令,向各事务发送提交指令。如果收到一个事务不可提交的指令,或者等待一个事务PreCommit响应超时时,也发送中断事务。各事务收到协调者指令后,执行指令释放资源锁,如果在此阶段没有收到协调者指令,等待超时,自己会继续执行提交事务。
3pc问题:由于网络问题,导致在第三阶段,一个事务收到指令进行回滚,另一个没有收到进行提交,导致数据不一致。
3.3 TCC
TCC和2pc、3pc一样,也是一种比较成熟的分布式事务解决方案。TCC包括Try、Confirm、Cancel三个阶段。
TCC有两种实现。
第一种:
- try:执行真正的业务,如insert一条数据
- confirm:确认数据,将try阶段的数据查询出来确认,如select数据确认是否已ok
- cancel:将try阶段的数据做逆向取消,如delete from删除数据
第二种: - Try:负责资源的检查和预留,通过一个冻结状态,做排它处理
- Confirm:提交,执行真正的业务:
- Cancel: 恢复资源状态,取消资源的冻结状态
3.4 paxos算法
paxos算法是解决分布式环境下数据一致性问题的经典算法。多个节点并发操纵数据,如何保证在读写过程中数据的一致性,并且解决方案要能适应分布式环境下的不可靠性(系统如何就一个值达到统一)。
paxos算法中三个角色:
- Proposer(提案者):提交提案,比如修改一个字段的值
- Acceptor(接受者):对提案作出裁决,比如接受提案、否定提案
- Learners(学习者):负责学习提案结果
在分布式中,一个进程可以是Proposer来,也可以是Acceptor或者Learners。paxos算法中对于一个提案,不需要所有接受者都接受,只要半数以上的接受,这个提案就被接受了。所以paxos算法也就是多数者协议,从这里看出来,paxos算法中的成员必须是奇数。
那如果有多个提案,怎么保证只有一个提案被接受,并且如果某个进程认为某个提案被选定了,那么其他进程必须接受。
paxos算法也分为两个阶段:
(1)prepare阶段
Proposer提交一个编号为n,值为v1的提案,向半数以上的Acceptor发送编号为N的Prepare请求。一个Acceptor收到一个编号为n的提案,如果这个编号n小于它之前响应过的请求,则拒绝,不回应或回复error。如果编号n大于它之前响应过的请求,它将自己之前接受的最大提案的值v2返回。Acceptor收到过半的请求后,如果发现有值返回,说明已经有接受的提案了,则重新发起一个编号为n,值为v2的Accept请求,如果没有值返回,就把自己的值v1当提案提交。如果没有收到Acceptor回应或者半数以上拒绝,则增大提案编号,重新发起prepare请求。
(2)accept阶段
如果一个Proposer收到半数以上Acceptor对其发出的编号为n的Prepare请求的响应,那么它就会发送一个编号n,值为v2的Acceptor。如果Acceptor收到一个针对编号为n的提案的Accept请求,只要n大于它响应过的提案的最大编号,它就接受该提案。如果小于Acceptor则拒绝,不回应或回复error。当proposer没有收到过半的回应,增大提案编号,重新发起prepare请求。
最后还有个学习阶段,也就是学习者要保持接受的提案,这里简单说下有三种方式: - 一个Acceptor接收提案就发送给其他Learners,优点:Learners更新快;缺点:通信太多
- 一个Acceptor接收提案就发送主Learner,主Learner给其他Learners,优点:通信减少了;缺点:通单点故障
- 一个Acceptor接收提案就发送主给Learner集合,Learner集合发给其他Learners,优点:可靠了;缺点:通信复杂
3.5 zab算法
Zab(Zookeeper Atomic Broadcast )协议全称是Zookeeper原子广播,通过 Zab 协议来保证分布式事务的最终一致性。
Zookeeper就是基于Zab协议来实现数据一致性,zk实现了一种主备模型来保证集群中各个副本之间数据的一致性。主备模型是指有一台Leader负责处理外部的写事务请求,然后Leader将数据同步到其他Follower节点。读事务所有节点(不管是Leader还是Follower)都可以执行。
Leader三个阶段
Zab协议要求每个 Leader 都要经历三个阶段:发现,同步,广播。
- 发现:zk集群必须选举出一个 Leader 进程,同时Leader会维护一个Follower可用客户端列表。将来客户端可以和这些 Follower节点进行通信。
- 同步:Leader 要负责将本身的数据与Follower同步。
- 广播:Leader接受客户端事务Proposal请求,将Proposal请求广播给所有的 Follower。
3.5.1 广播
(1)客户端发起一个写操作请求,如果Follower 接收到将转发给Leader 。
(2)Leader 服务器将客户端的请求转化为事务 Proposal 提案,同时为每个 Proposal 分配一个全局的ID,即zxid。
(3)每个Follower 服务器都有一个单独的队列,Leader将需Proposal 依次放到队列中取,并且根据 FIFO 策略进行消息发送。
(4)Follower接收到Proposal后,会先将其写入事务日志,成功后向 Leader 反馈一个Ack响应。
(5)Leader接收到超过半数以Follower的Ack响应后,向Follower发送commit消息。
(6)Leader向所有Follower广播commit消息,同时自身commit。Follower接收到commit 消息后,将事务提交。
3.5.2 崩溃恢复
Zab 协议的特性:
- Zab 协议需要确保那些已经在一个服务器上提交(Commit)的事务最终被所有的服务器提交。
- Zab 协议需要确保丢弃那些只在 Leader 上被提出而没有被提交的事务。
- 全局有序(Total order) : 假设有一台server先执行事务A再执行事务B,那么可以所有server都会先执行事务A再执行事务B
先说下什么是zxid。
zk会为每一个事务生成一个长度为64位的ZXID,ZXID由两部分组成:低32位表示计数器(counter)和高32位的Leader标识(epoch)。epoch为当前leader在成为leader的时候生成的,且保证会比前一个leader的epoch大,一般都是在前一个epoch+1。
并且一个新leader会将计数器置0。随后每接受一个新事务加1.
根据Zab 协议的特性,新选举出来的leader不能包含未提交的proposal,即新选举的leader必须都是已经提交了的proposal的follower服务器节点。同时,新选举的leader节点中含有最高的ZXID。这样做的好处就是可以避免了leader服务器检查proposal的提交和丢弃工作。
leader服务器发生崩溃时分为如下场景:
(1)当leader在commit之后但在发出commit消息之前宕机,即只有老leader自己commit了,而其它follower都没有收到commit消息 新的leader也必须保证这个proposal被提交。
zab协议怎么保证的?
zab协议保证新选举的leader的本地proposal事务必须全部提交,并且leader必须包含最大的ZXID。
(2)leader在提出proposal后宕机,其他follower还没有收到。当这个leader恢复后,新leader保证原有leader丢掉原有proposal。
zab协议怎么保证的?
原有leader恢复后,在和新leader通信后,发现自己有个比新leader的ZXID更大的未提交事务。新leader会要求原有leader恢复到上一个新leader提交的位置。
3.5.3 数据同步
当一个新leader选举出来后,follower将自己额最大ZXID发给leader,leader将follower没有的proposal重新发送,并随后发送commit命令。大多数的follower同步数据完成后,leader才开始接受新的事务。
一个新的follower加入集群后,也要同步完数据后,才会被leader加入可用列表。
3.5.4 节点的状态
- Following:当前节点是跟随者,服从 Leader 节点的命令
- Leading:当前节点是 Leader,负责协调事务
- Election/Looking:节点处于选举状态,正在寻找 Leader
- Observing:Observer 不参与选举,是只读节点,跟 Zab 协议没有关系