第1章Zookeeper理论基础

分布式协调服务器Zookeeper

第1章Zookeeper理论基础

Zookeeper 简介

ZooKeeper 由雅虎研究院开发,后来捐赠给了Apache。ZooKeeper 是一个开源的分布式应用程序协调服务器,其为分布式系统提供一致性服务。其一致性是通过基于Paxos 算法的ZAB 协议完成的。其主要功能包括:配置维护、域名服务、分布式同步、集群管理等

zookeeper 的官网:http://zookeeper.apache.org

image-20200329073617838

一致性

zk 是如何保证分布式系统的一致性的呢?是因为zk 具有以下几方面的特点:

顺序一致性

从同一个客户端发起的多个事务请求(写操作请求),最终会严格按照客户端发起顺序记录到zk中

原子性

所有事务请求的结果在集群中所有Server上的应用情况是一致的。要么全部应用成功,要么都没有成功,不会出现部分成功,部分失败的情况

单一视图

无论客户端连接的是集群中的哪台Server,其读取到的数据模型中的数据都是一致的

可靠性

一旦某事务被成功应用到了zk,则会一直被保留下来,除非另一个事务将其修改

最终一致性

一旦一个事务被成功应用,zk 可以保证在一段较短的时间内,客户端最终一定能够从服务端读取到最新的数据,但不能保证实时读取到,所以满足最终一致性,而不是实时一致性

Paxos 算法

对于zk理论的学习,最重要也是最难的知识点就是Paxos 算法。所以我们首先学习Paxos算法

算法简介

Paxos 算法是莱斯利·兰伯特(Leslie Lamport)1990 年提出的一种基于消息传递的、具有高容错性的一致性算法。Google Chubby 的作者Mike Burrows 说过,世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos 算法的不完整版。Paxos 算法是一种公认的晦涩难懂的算法,并且工程实现上也具有很大难度。较有名的Paxos 工程实现有Google Chubby、ZAB、微信的PhxPaxos 等

Paxos 算法是用于解决什么问题的呢?

Paxos 算法要解决的问题是,在分布式系统中如何就某个决议达成一致

Paxos与拜占庭将军问题

拜占庭将军问题是在不可靠信道上试图通过消息传递的方式达到一致性是不可能的。

拜占庭将军问题是由Paxos 算法作者莱斯利·兰伯特提出的点对点通信中的基本问题。该问题要说明的含义是,在不可靠信道上试图通过消息传递的方式达到一致性是不可能的。所以,Paxos 算法的前提是不存在拜占庭将军问题,即信道是安全的、可靠的,集群节点间传递的消息是不会被篡改的

一般情况下,分布式系统中各个节点间采用两种通讯模型:共享内存(Shared Memory)、消息传递(Messages Passing)。而Paxos 是基于消息传递通讯模型的

共享内存和消息传递这两种通讯模式

并发模型 通信机制 同步机制
共享内存 线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信 同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行
消息传递 线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信 由于消息的发送必须在消息的接收之前,因此同步是隐式进行的

共享内存

共享内存这种方式比较常见,我们经常会设置一个共享变量。然后多个线程去操作同一个共享变量。从而达到线程通讯的目的。例如,我们使用多个线程去执行页面抓取任务,我们可以使用一个共享变量count来记录任务完成的数量。每当一个线程完成抓取任务,会在原来的count上执行加1操作。这样每个线程都可以通过获取这个count变量来获得当前任务的完成情况。当然必须要考虑的是共享变量的同步问题,这也共享内存容易出错的原因所在。

这种通讯模型中,不同的线程之间是没有直接联系的。都是通过共享变量这个“中间人”来进行交互。而这个“中间人”必要情况下还需被保护在临界区内(加锁或同步)。由此可见,一旦共享变量变得多起来,并且涉及到多种不同线程对象的交互,这种管理会变得非常复杂,极容易出现死锁等问题。

消息传递

消息传递方式采取的是线程之间的直接通信,不同的线程之间通过显式的发送消息来达到交互目的。消息传递最有名的方式应该是actor模型了。在这种模型下,一切都是actor,所有的actor之间的通信都必须通过传递消息才能达到。每个actor都有一个收件箱(消息队列)用来保存收到其他actor传递来的消息。actor自己也可以给自己发送消息。这才是面向对象的精髓啊!

这种模型看起来比共享内存模型要复杂。但是一旦碰到复杂业务的话,actor模型的优势就体现出来了。我们还是以刚才多线程抓取网站为例子看一下在这种模型下如何去解决。

首先我们定义一个统计actor用来统计任务完成量。然后把多个网址(消息方式)发给多个抓取actor,抓取actor处理完任务后发送消息通知统计actor任务完成,统计actor对自己保存的变量count(这个只有统计actor才能看到)加一。

算法描述

三种角色

在Paxos 算法中有三种角色,分别具有三种不同的行为。但很多时候,一个进程可能同时充当着多种角色

  • Proposer:提案者
  • Acceptor:表决者
  • Learner:同步者
Paxos 算法的一致性

Paxos 算法的一致性主要体现在以下几点:

  • 每个提案者在提出提案时都会首先获取到一个具有全局唯一性的、递增的提案编号N,即在整个集群中是唯一的编号N,然后将该编号赋予其要提出的提案
  • 每个表决者在accept 某提案后,会将该提案的编号N 记录在本地,这样每个表决者中保存的已经被accept 的提案中会存在一个编号最大的提案,其编号假设为maxN。每个表决者仅会accept 编号大于自己本地maxN 的提案
  • 在众多提案中最终只能有一个提案被选定
  • 一旦一个提案被选定,则其它服务器会主动同步(Learn)该提案到本地
  • 没有提案被提出则不会有提案被选定。
算法过程描述

Paxos 算法的执行过程划分为两个阶段:准备阶段prepare 与接受阶段accept

image-20200329082258747

prepare 阶段

1) 提案者(Proposer)准备提交一个编号为N 的提议,于是其首先向所有表决者(Acceptor)发送prepare(N)请求,用于试探集群是否支持该编号的提议

2) 每个表决者(Acceptor)中都保存着自己曾经accept 过的提议中的最大编号maxN。当一个表决者接收到其它主机 发送来的prepare(N)请求时,其会比较N 与maxN 的值。有以下几种情况:

​ a) 若N 小于maxN,则说明该提议已过时,当前表决者采取不回应或回应Error 的方式来拒绝该prepare 请求

​ b) 若N 大于maxN,则说明该提议是可以接受的,当前表决者会首先将该N 记录下来,并将其曾经已经accept 的编号最大的提案Proposal(myid,maxN,value)反馈给提案者,以向提案者展示自己支持的提案意愿。其中第一个参数myid 表示该提案的提案者标识id,第二个参数表示其曾接受的提案的最大编号maxN,第三个参数表示该提案的真正内容value。当然,若当前表决者还未曾accept 过任何提议,则会将Proposal(myid,null,null)反馈给提案者

​ c) 在prepare 阶段N 不可能等于maxN。这是由N 的生成机制决定的。要获得N 的值,其必定会在原来数值的基础上采用同步锁方式增一

accept 阶段

1) 当提案者(Proposer)发出prepare(N)后,若收到了超过半数的表决者(Accepter)的反馈,那么该提案者就会将其真正的提案Proposal(myid,N,value)发送给所有的表决者

2) 当表决者(Acceptor)接收到提案者发送的Proposal(myid,N,value)提案后,会再次拿出自己曾经accept 过的提议中的最大编号maxN,或曾经记录下的prepare 的最大编号,让N与它们进行比较,若N 大于等于这两个编号,则当前表决者accept 该提案,并反馈给提案者。若N 小于这两个编号,则表决者采取不回应或回应Error 的方式来拒绝该提议。

3) 若提案者没有接收到超过半数的表决者的accept 反馈,则有两种可能的结果产生:一是放弃该提案,不再提出;二是重新进入prepare 阶段,递增提案号,重新提出prepare请求

4) 若提案者接收到的反馈数量超过了半数,则其会向外广播两类信息

​ a) 向曾accept 其提案的表决者发送“可执行数据同步信号”,即让它们执行其曾接收到的提案

​ b) 向未曾向其发送accept 反馈的表决者发送“提案+ 可执行数据同步信号”,即让它们接受到该提案后马上执行

注:

ACK (Acknowledge character)即是确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。

在TCP/IP协议中,如果接收方成功的接收到数据,那么会回复一个ACK数据。通常ACK信号有自己固定的格式,长度大小,由接收方回复给发送方。


prepare、proposal、commit

提问1:

在Prepare 阶段已经比较过了,并且已经通过了,为什么在Accept 阶段还需要进行比较?

当Prepare 阶段结束时没有直接执行Accept,而是直接又进来一个新的提案,这时,新的提案使maxN的值发生改变,此时再进行之前的Accept阶段就不能继续进行

提问2:

在Prepare 阶段与Accept 阶段都进行了比较,为什么在发送COMMIT 信号量时无需进行比较?

这里存在一个机会主义,前面已经确认两次了,第三次被卡掉的几率会低一些,就如同三次握手一样,已经握手三次,则接下来出现问题的几率不大,但也有可能出现问题

此时就会进行如下操作,一旦进入accept阶段并且acceptor同意修改,只要同意,就将acceptor的状态修改为Learning,此时这个acceptor就不对外提供服务,也就是说它不能提交提案,也不能对其他主机提交的提案进行决议

只要修改为Learing,就相当于将其锁定

Paxos算法的活锁问题

活锁:

活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开。

前面所述的Paxos 算法在实际工程应用过程中,根据不同的实际需求存在诸多不便之处,所以也就出现了很多对于基本Paxos 算法的优化算法,以对Paxos 算法进行改进,例如,MultiPaxos、Fast Paxos、EPaxos。

例如,Paxos 算法存在“活锁问题”,Fast Paxos 算法对Paxos 算法进行了改进:只允许一个进程提交提案,即该进程具有对N 的唯一操作权。该方式解决了“活锁”问题

注:

死锁

ZAB 协议

ZAB协议简介

ZAB ,Zookeeper Atomic Broadcast,zk 原子消息广播协议,是专为ZooKeeper 设计的一种支持崩溃恢复的原子广播协议,在Zookeeper 中,主要依赖ZAB 协议来实现分布式数据一致性。

Zookeeper 使用一个单一主进程来接收并处理客户端的所有事务请求,即写请求。当服务器数据的状态发生变更后,集群采用ZAB 原子广播协议,以事务提案Proposal 的形式广播到所有的副本进程上。ZAB 协议能够保证一个全局的变更序列,即可以为每一个事务分配一个全局的递增编号xid。

当Zookeeper 客户端连接到Zookeeper 集群的一个节点后,若客户端提交的是读请求,那么当前节点就直接根据自己保存的数据对其进行响应;如果是写请求且当前节点不是Leader,那么节点就会将该写请求转发给Leader,Leader 会以提案的方式广播该写操作,只要有超过半数节点同意该写操作,则该写操作请求就会被提交。然后Leader 会再次广播给所有订阅者,即Learner,通知它们同步数据。

image-20200427230313604

ZAB与Paxos的关系

ZAB 协议是Paxos 算法的一种工业实现算法。但两者的设计目标不太一样。ZAB 协议主要用于构建一个高可用的分布式数据主从系统,即Follower 是Leader 的从机,Leader 挂了,马上就可以选举出一个新的Leader,但平时它们都对外提供服务。而Fast Paxos 算法则是用于构建一个分布式一致性状态机系统,确保系统中各个节点的状态都是一致的。

  • 只允许leader处理写请求时Fast Paxos算法的体现,因为只允许一个leader处理写请求

  • 而leader选举是Paxos算法的一种体现

实际上这里面是两种算法的体现

主从系统:主和从平时都对外提供服务,读写分离集群

主备系统:主对外提供服务,但是备不对外提供服务,不管是读还是写,都是只对主进行操作,备中只做实时同步

三类角色

为了避免Zookeeper 的单点问题,zk 也是以集群的形式出现的。zk 集群中的角色主要有以下三类:

  • Leader:事务请求的唯一处理者,也可以处理读请求。

  • Follower:可以直接处理客户端的读请求,并向客户端响应,但其不会处理事务请求,其只会将客户端事务请求转发给Leader 来处理。对Leader 发起的事务提案具有表决权;同步Leader 中的事务处理结果;Leader 选举过程的参与者,具有选举权与被选举权。(就好像正式工)

  • Observer:可以理解为不参与Leader 选举的Follower,在Leader 选举过程中没有选举权与被选举权,同时,对于Leader 的提案没有表决权。用于协助Follower 处理更多的客户端读请求。

    Observer 的增加,会提高集群读请求处理的吞吐量,但不会增加事务请求的通过压力,不会增加Leader 选举的压力。(就好像临时工)

    注:

    Observer 设置为多少合适?是否是越多越好?

这三类角色在不同的情况下又有一些不同的名称:

  • Learner:学习者,即要从Leader 中同步数据的Server,即Follower 与Observer。
    Learner = Follower + Observer

  • QuorumServer:(又称为QuorumPeer、Participant),翻译为中文即法定服务器、法定主机、参与者。

    在集群正常服务状态下,具有表决权的服务器称为QuorumServer,或QuorumPeer

    在Leader选举过程中,具有选举权与被选举权的服务器,称为Participant。
    QuorumServer = Leader + Follower = Participant

三个数据

在ZAB 中有三个很重要的数据:

  • zxid:其为一个64 位长度的Long 类型,其中高32 位表示epoch,低32 位表示xid。
  • epoch:表示时期、年号,每个Leader 选举结束后都会生成一个新的epoch,并会通知到集群中所有其它Server,包含Follower 与Observer。
  • xid:事务id,是一个流水号。只会增加

高32位会变,是因为选举出新的leader,所以改变,低32位是来一个事务就会改变

三种模式

ZAB 协议中对zkServer 的状态描述有三种模式。这三种模式并没有十分明显的界线,它们相互交织在一起。

  • 恢复模式:在集群启动过程中,或Leader 崩溃后,系统都需要进入恢复模式,以恢复系统对外提供服务的能力。其包含两个重要阶段:Leader 选举与初始化同步。
  • 广播模式:其分为两类:初始化广播与更新广播。站在Leader的角度是广播
  • 同步模式:其分为两类:初始化同步与更新同步。站在Learner的角度是同步,Learner要从Leader那里同步数据

四种状态

zk 集群中的每一台主机,在不同的阶段会处于不同的状态。每一台主机具有四种状态。

  • LOOKING:选举状态,在Leader宕机或者重启的时候,所有的主机都会处于Looking状态,直到选举出新的Leader以后
  • FOLLOWING:Follower 的正常工作状态
  • OBSERVING:Observer 的正常工作状态
  • LEADING:Leader 的正常工作状态

在ZAB中FOLLOWING和OBSERVING状态相当于LEARNING

在ZAB中LEADING状态相当于BROADCASTING(过半状态提交commit状态的时候)

在纯理论中,使用LEARNING和BROADCASTING

在实际落地实践中,使用FOLLOWING、OBSERVING、LEADING

同步模式与广播模式

初始化广播

前面我们说过,恢复模式具有两个阶段:Leader 选举与初始化同步(广播)。当完成Leader选举后,此时的Leader 还是一个准Leader,其要经过初始化同步后才能变为真正的Leader。

image-20200427230827488

具体过程如下:
1) 为了保证Leader 向Learner 发送提案的有序,Leader 会为每一个Learner 服务器准备一个队列

2) Leader 将那些没有被各个Learner 同步的事务封装为Proposal,这里的事务是指Leader中所有的事务而Learner中没有的事务

3) Leader 将这些Proposal 逐条发给各个Learner,并在每一个Proposal 后都紧跟一个COMMIT 消息,表示该事务已经被提交,Learner 可以直接接收并执行

4) Learner 接收来自于Leader 的Proposal,并将其更新到本地

5) 当Learner 更新成功后,会向准Leader 发送ACK 信息

6) Leader 服务器在收到来自Learner 的ACK 后就会将该Learner 加入到真正可用的Follower列表或Observer 列表。没有反馈ACK,或反馈了但Leader 没有收到的Learner,Leader不会将其加入到相应列表。

消息广播算法

image-20200427230956690

当集群中的Learner 完成了初始化状态同步,那么整个zk 集群就进入到了正常工作模式了。

如果集群中的Learner 节点收到客户端的事务请求,那么这些Learner 会将请求转发给Leader 服务器。然后再执行如下的具体过程:

1) Leader 接收到事务请求后,为事务赋予一个全局唯一的64 位自增id,即zxid,通过zxid 的大小比较即可实现事务的有序性管理,然后将事务封装为一个Proposal。

2) Leader 根据Follower 列表获取到所有Follower,然后再将Proposal 通过这些Follower 的队列将提案发送给各个Follower。

3) 当Follower 接收到提案后,会先将提案的zxid 与本地记录的事务日志中的最大的zxid进行比较。若当前提案的zxid 大于最大zxid,则将当前提案记录到本地事务日志中,并向Leader 返回一个ACK。(提问学员)

4) 当Leader 接收到过半的ACKs 后,Leader 就会向所有Follower 的队列发送COMMIT消息,向所有Observer 的队列发送Proposal。

5) 当Follower 收到COMMIT 消息后,就会将日志中的事务正式更新到本地。当Observer收到Proposal 后,会直接将事务更新到本地。

6) 无论是Follower 还是Observer,在同步完成后都需要向Leader 发送成功ACK。

问题:

1为什么proposal-zxid还要与max-zxid进行比较?

ABA问题

2

Observer 的数量问题

Observer 数量一般与Follower 数量相同。并不是Observer 越多越好,因为Observer 数量的增多虽不会增加事务操作压力,但其需要从Leader 同步数据,Observer 同步数据的时间是小于等于Follower 同步数据的时间的。当Follower 同步数据完成,Leader 的Observer列表中的Observer 主机将结束同步。那些完成同步的Observer 将会进入到另一个对外提供服务的列表。那么,那些没有同步了数据无法提供服务的Observer 主机就形成了资源浪费。

所以,对于事务操作发生频繁的系统,不建议使用过多的Observer。

  • Leader 中存在两个关于Observer 的列表:all(包含所有Observer)与service(包含与Leader 同步过数据的Observer)
  • service 列表是动态变化的。对于没有进入到service 列表中的Observer,其会通过心跳与Leader 进行连接,一旦连接成功,马上就会从Leader 同步数据,同步完成后向Leader发送ACK。Leader 在接收到其ACK 后会将其添加到service 列表。
  • 若客户端连接上了不在service 列表中的Observer,那么这个Observer 是不能提供服务的。因为该Observer 的状态不是Observering。这个状态是通过Observer 与Leader 间的心跳来维护的。
  • Leader 中对于Follower 也同样存在两个列表:all 与service。其功能与Observer 的相似。但不同点是,若Leader 收到的Follower 同步完成的ACK 数量没有过半,则认为同步失败,会重新进行广播,让Follower 重新进行同步。

Observer的应用场景

读操作请求很多,而写操作或者事务请求较少,在这样的场景下可以多增加一些

恢复模式的三个原则

当集群正在启动过程中,或Leader 崩溃后,集群就进入了恢复模式。对于要恢复的数据状态需要遵循三个原则。

Leader 的主动出让原则

若集群中Leader 收到的Follower 心跳数量没有过半,此时Leader 会自认为自己与集群的连接已经出现了问题,其会主动修改自己的状态为LOOKING,去查找新的Leader。为了防止集群出现脑裂。

而其它Server 由于有过半的主机认为已经丢失了Leader,所以它们会发起新的Leader选举,选出一个新的Leader。

已被处理过的消息不能丢原则

当Leader 收到超过半数Follower 的ACKs 后,就向各个Follower 广播COMMIT 消息,批准各个Server 执行该写操作事务。当各个Server 在接收到Leader 的COMMIT 消息后就会在本地执行该写操作,然后会向客户端响应写操作成功。

但是如果在非全部Follower 收到COMMIT 消息之前Leader 就挂了,这将导致一种后果:部分Server 已经执行了该事务,而部分Server 尚未收到COMMIT 消息,所以其并没有执行该事务。当新的Leader 被选举出,集群经过恢复模式后需要保证所有Server 上都执行了那些已经被部分Server 执行过的事务。

Leader的选举算法:先比较zxid,谁的zxid大,谁当选,当zxid相同的时候,比较serverid,即主机的id,谁的serverid大谁当选

初始化同步有可能要比更新同步的时间要长

被丢弃的消息不能再现原则

当在Leader 新事务已经通过,其已经将该事务更新到了本地,但所有Follower 还都没有收到COMMIT 之前,Leader 宕机了(比前面叙述的宕机更早),此时,所有Follower 根本就不知道该Proposal 的存在。当新的Leader 选举出来,整个集群进入正常服务状态后,之前挂了的Leader 主机重新启动并注册成为了Follower。若那个别人根本不知道的Proposal还保留在那个主机,那么其数据就会比其它主机多出了内容,导致整个系统状态的不一致。所以,该Proposa 应该被丢弃。类似这样应该被丢弃的事务,是不能再次出现在集群中的,应该被清除。

Leader选举

Leader选举在整个恢复模式中是最重要的阶段,这里就是为以后看源码打基础

在集群启动过程中,或Leader 宕机后,集群就进入了恢复模式。恢复模式中最重要的阶段就是Leader 选举。

Leader 选举中的基本概念

myid

在代码中也称为ServerId,这是zk 集群中服务器的唯一标识。例如,有三个zk 服务器,那么编号分别是1,2,3。

逻辑时钟

逻辑时钟,Logicalclock,是一个整型数,该概念在选举时称为logicalclock,而在选举结束后称为epoch。即epoch 与logicalclock 是同一个值,在不同情况下的不同名称。

Leader 选举算法

在集群启动过程中的Leader 选举过程(算法)与Leader 断连后的Leader 选举过程稍微有一些区别,基本相同。

集群启动中的Leader选举

若进行Leader 选举,则至少需要两台主机,这里以三台主机组成的集群为例。

image-20200427231453793

在集群初始化阶段,当第一台服务器Server1 启动时,其会给自己投票,然后发布自己的投票结果。投票包含所推举的服务器的myid 和ZXID,使用(myid, ZXID)来表示,此时Server1的投票为(1, 0)。由于其它机器还没有启动所以它收不到反馈信息,Server1 的状态一直属于Looking,即属于非服务状态。

当第二台服务器Server2 启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,选举过程如下:

(1) 每个Server 发出一个投票。此时Server1 的投票为(1, 0),Server2 的投票为(2, 0),然后各自将这个投票发给集群中其他机器。

(2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性(即检查选举人和被选举人的合法性),如检查是否是本轮投票、是否来自LOOKING 状态的服务器。

(3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK

规则如下:

  • 优先检查ZXID。ZXID 比较大的服务器优先作为Leader。
  • 如果ZXID 相同,那么就比较myid。myid 较大的服务器作为Leader 服务器。

对于Server1 而言,它的投票是(1, 0),接收Server2 的投票为(2, 0)。其首先会比较两者的ZXID,均为0,再比较myid,此时Server2 的myid 最大,于是Server1 更新自己的投票为(2, 0),然后重新投票。对于Server2 而言,其无须更新自己的投票,只是再次向集群中所有主机发出上一次投票信息即可。

(4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息。对于Server1、Server2 而言,都统计出集群中已经有两台主机接受了(2, 0)的投票信息,此时便认为已经选出了新的Leader,即Server2。

(5) 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。

(6) 添加主机。在新的Leader 选举出来后Server3 启动,其想发出新一轮的选举。但由于当前集群中各个主机的状态并不是LOOKING,而是各司其职的正常服务,所以其只能是以Follower 的身份加入到集群中。

宕机后的Leader选举

在Zookeeper 运行期间,Leader 与非Leader 服务器各司其职,即便当有非Leader 服务器宕机或新加入时也不会影响Leader。但是若Leader 服务器挂了,那么整个集群将暂停对外服务,进入新一轮的Leader 选举,其过程和启动时期的Leader 选举过程基本一致。

image-20200427231655100

假设正在运行的有Server1、Server2、Server3 三台服务器,当前Leader 是Server2,若某一时刻Server2 挂了,此时便开始新一轮的Leader 选举了。选举过程如下:

(1) 变更状态。Leader 挂后,余下的非Observer 服务器都会将自己的服务器状态由
FOLLOWING 变更为LOOKING,然后开始进入Leader 选举过程。

(2) 每个Server 会发出一个投票,仍然会首先投自己。不过,在运行期间每个服务器上的ZXID 可能是不同,此时假定Server1 的ZXID 为111,Server3 的ZXID 为333;在第一轮投票中,Server1 和Server3 都会投自己,产生投票(1, 111),(3, 333),然后各自将投票发送给集群中所有机器。

(3) 接收来自各个服务器的投票。与启动时过程相同。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING 状态的服务器。

(4) 处理投票。与启动时过程相同。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK。对于Server1 而言,它的投票是(1, 111),接收Server3 的投票为(3, 333)。

其首先会比较两者的ZXID,Server3 投票的zxid 为333 大于Server1 投票的zxid 的111,于是Server1 更新自己的投票为(3, 333),然后重新投票。对于Server3 而言,其无须更新自己的投票,只是再次向集群中所有主机发出上一次投票信息即可。

(5) 统计投票。与启动时过程相同。对于Server1、Server2 而言,都统计出集群中已经有两台主机接受了(3, 333)的投票信息,此时便认为已经选出了新的Leader,即Server3。

(6) 改变服务器的状态。与启动时过程相同。一旦确定了Leader,每个服务器就会更新自己的状态。Server1 变更为FOLLOWING,Server3 变更为LEADING。

高可用集群的容灾

注意是容灾

服务器数量的奇数与偶数

前面我们说过,无论是写操作投票,还是Leader 选举投票,都必须过半才能通过,也就是说若出现超过半数的主机宕机,则投票永远无法通过。基于该理论,由5 台主机构成的集群,最多只允许2 台宕机。而由6 台构成的集群,其最多也只允许2 台宕机。即,6 台与5 台的==容灾能力==是相同的。基于此容灾能力的原因,建议使用奇数台主机构成集群,以避免资源浪费。

但从系统吞吐量上说,6 台主机的性能一定是高于5 台的。所以使用6 台主机并不是资源浪费。

容灾设计方案

对于一个高可用的系统,除了要设置多台主机部署为一个集群避免单点问题外,还需要考虑将集群部署在多个机房、多个楼宇。对于多个机房、楼宇中集群也是不能随意部署的,下面就多个机房的部署进行分析。
在多机房部署设计中,要充分考虑“过半原则”,也就是说,尽量要确保zk 集群中有过半的机器能够正常运行。

三机房部署

在生产环境下,三机房部署是最常见的、容灾性最好的部署方案。

三机房部署中要求每个机房中的主机数量必须少于集群总数的一半。这样可以保证,三个机房中若有一个机房断电或断网,其它两个机房中的机器总数仍是过半的,集群仍可以正常对外提供服务。当然,若两个机房出现了问题,那么整个集群就瘫痪了。这种情况出现的概率要远低于一个机房出问题的情况。

双机房部署

zk 官网没有给出较好的双机房部署的容灾方案。只能是让其中一个机房占有超过半数的主机,使其做为主机房,而另一机房少于半数。当然,若主机房出现问题,则整个集群会瘫痪。

CAP 定理

简介

CAP 定理指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、
Partition tolerance(分区容错性),三者不可兼得。

  • 一致性(C):分布式系统中多个主机之间是否能够保持数据一致的特性。即,当系统数据发生更新操作后,各个主机中的数据仍然处于一致的状态。集群中不存在实时一致性(强一致性、严格一致性)

  • 可用性(A):系统提供的服务必须一直处于可用的状态,即对于用户的每一个请求,系统总是可以在有限的时间内对用户做出响应。百度在0.5秒内给出响应,谷歌是0.3秒

  • 分区容错性(P):分布式系统在遇到任何网络分区故障时,仍能够保证对外提供满足一致性和可用性的服务。比如说三个机房,其中两个之间的网络断开了,但是对外依然能够提供一致性服务,这就是分区容错性

对于分布式系统,网络环境相对是不可控的,出现网络分区是不可避免的,因此系统必须具备分区容错性。但其并不能同时保证一致性与可用性。CAP 原则对于一个分布式系统来说,只可能满足两项,即要么CP,要么AP。

BASE理论

BASE 是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写。是CAP 定理对于一致性与可用性权衡的结果。

BASE 理论的核心思想是:即使无法做到强一致性,但每个系统都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

基本可用

基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
可以是响应时间的损失,也可以是功能上的损失来保证程序的可用

软状态

软状态,是指允许系统数据存在的中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统主机间进行数据同步的过程存在一定延时。软状态,其实就是一种灰度状态,过渡状态。

最终一致性

最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

ZK与CP

zk 遵循的是CP 原则,即保证了一致性,但牺牲了可用性。体现在哪里呢?
当Leader 宕机后,zk 集群会马上进行新的Leader 的选举。但选举时长一般在200 毫秒内,最长不超过60 秒,整个选举期间zk 集群是不接受客户端的读写操作的,即zk 集群是处于瘫痪状态的。所以,其不满足可用性。

Eureka 保证了AP,牺牲了CP。即其保证了可用性,但无法保证一致性。

Zookeeper在Dubbo中做注册中心的,Eureka在SpringCloud中也做注册中心的

zk 可能会存在脑裂

这里说的zk 可能会引发脑裂,是指的在多机房部署中,若出现了网络连接问题,形成多个分区,则可能会出现脑裂问题,可能会导致数据不一致。

文章目录
  1. 1. 分布式协调服务器Zookeeper
    1. 1.1. 第1章Zookeeper理论基础
      1. 1.1.1. Zookeeper 简介
      2. 1.1.2. 一致性
      3. 1.1.3. Paxos 算法
        1. 1.1.3.1. 算法简介
        2. 1.1.3.2. Paxos与拜占庭将军问题
        3. 1.1.3.3. 算法描述
          1. 1.1.3.3.1. 三种角色
          2. 1.1.3.3.2. Paxos 算法的一致性
          3. 1.1.3.3.3. 算法过程描述
        4. 1.1.3.4. Paxos算法的活锁问题
      4. 1.1.4. ZAB 协议
        1. 1.1.4.1. ZAB协议简介
        2. 1.1.4.2. ZAB与Paxos的关系
        3. 1.1.4.3. 三类角色
        4. 1.1.4.4. 三个数据
        5. 1.1.4.5. 三种模式
        6. 1.1.4.6. 四种状态
        7. 1.1.4.7. 同步模式与广播模式
        8. 1.1.4.8. 恢复模式的三个原则
        9. 1.1.4.9. Leader选举
          1. 1.1.4.9.1. Leader 选举中的基本概念
          2. 1.1.4.9.2. Leader 选举算法
      5. 1.1.5. 高可用集群的容灾
        1. 1.1.5.1. 服务器数量的奇数与偶数
        2. 1.1.5.2. 容灾设计方案
      6. 1.1.6. CAP 定理
        1. 1.1.6.1. 简介
        2. 1.1.6.2. BASE理论
        3. 1.1.6.3. ZK与CP
      7. 1.1.7. zk 可能会存在脑裂
|