Zoookeeper内部原理

通过本文可以了解:

  1. Zab协议实现所依赖的网络特性
  2. Zookeeper是顺序一致性还是线性一致性
  3. Zookeeper集群部署的三种方式

Zoookeeper内部原理

名词解释

  1. Quorum:Zookeeper集群中的服务器形成一个quorum才可以正常运行,可以理解为一组服务器。

简介

本文简单介绍zookeeper的内部工作机制,大致包括以下几个主题:

  • 原子广播(Atomic Broadcast)
  • 一致性保证(Consistency Guarantees)
  • Quorums

原子广播(Atomic Broadcast)

Zookeeper的核心是原子广播协议,这个协议能保证集群里的数据处于一致状态。

提供的保障、属性以及一些定义

Zookeeper的原子广播协议提供如下保障:

  • 可靠投递(Reliable delivery):如果消息m被一个消息发送出来,那么所有的服务器最终都会收到这个消息并且保存。
  • 全序(Total order):如果一个服务器发送了消息a,之后又发送了消息b,那么其他服务器收到消息的顺序也是消息a、消息b。
  • 因果序(Causal order):如果b服务器先后发送了消息a和消息b,之后服务器a又发送了消息c,那么其他服务器接收顺序应该是a、b、c。

Zookeeper的消息系统必须是高效的、可靠的并且容易实现和维护的。我们在系统中大量地使用了消息,所以我们需要系统可以每秒处理上千的消息量。尽管我们要求最少一半以上的服务器存活才能保证系统的正常运行,但是当相关错误出现时,比如断电,我们的系统必须能够快速恢复。我们在实现这个系统时,我们只有很少的工期,并且研发资源有限,所以我们需要一个容易理解的、并且容易实现的协议。我们发现我们的协议非常适合我们的需求。

我们的协议假定我们可以在任何两个服务器之间建立FIFO消息队列。尽管和我们相似的产品经常假定消息分发会错序或者丢失消息,但是我们不会丢消息并且会保证顺序因为我们服务器之间使用的是TCP连接。TCP为我们提供了如下保证:

  1. 有序投递(Ordered delivery ):数据的接收顺序和数据的发送顺序是一样的,消息m只有在它之前的所有消息都被接收之后才会被接收(可以得到一个推论就是,如果消息m丢失,那么后面的所有消息也会丢失)。
  2. TCP连接关闭后不会再发送消息(No message after close):TCP连接关闭之后,消息不会再从连接里被接收到。

FLP证明在异步分布式系统中,如果存在任何异常的话,服务器之间不能达成共识。我们使用超时机制解决了这个问题(timeouts)。我们依赖时钟的正常工作,但是时间是否准确对我们来说并不重要(多个时钟时间一致),所以如果时钟出现异常(时间不一致)的话,整个消息系统会停滞,但是,系统还是会保证之前提供的几个属性(可靠投递、全序、因果序)。

当讨论Zookeeper的原子广播协议的时候,我们一般会讲数据包(packets)、提议(proposals)和消息(messages):

  1. 数据包(packets):通过TCP连接发送的字节序列。
  2. 提议(proposals):表示一个提议。如果发送的提议被集群里一半以上的服务器确认的话表示提议被接受。大多数提议都会带有消息,但是NEW_LEADER提议不会携带消息。
  3. 消息(messages):表示被发送到所有Zookeeper服务器的一个消息(字节序列)。一个消息在被放在一个提议中,并且在被大部分服务器确认后,才会被处理。

如之前表述,Zookeeper既然能保证消息的全序,当然也会保证提议的全序性质。Zookeeper使用事务id(zxid)来保证这种全序的性质。当主服务器进行提议时,它为这个提议设置一个zxid,这个zxid用来反映提议的先后顺序。提议会被发送到所有的Zookeeper服务器,当被大部分服务器确认后,这个提议就会被提交,当提议包含有消息时,这个消息会被服务器存储。我们的Majority quorum要求任何两个quorum的交集最少有一个服务器。我们要求一个Quorum最少包含Zookeeper集群服务器数量的一半加上一((n/2+1,n是服务器数量)。

zxid包含两部分:一部分表示经历过选主过程的次数(epoch),一部分表示某个主服务器服务期间,写操作的次数(counter)。在我们的实现中,zxid是64位数字(Long)。高32位表示epoch,低32表示counter。epoch表示主服务器变更的次数,每当主服务器变化的时候,epoch都会变化。我们使用一个简单算法用来保证每个提议都会有一个唯一的zxid,主服务器对每一个提议都会顺序递增。主服务器的领导机制可以保证同一时刻只有一个主服务器使用一个给定的epoch,所以我们的算法可以保证每一个提议都会有一个唯一的id。

Zookeeper消息处理大致分为两个步骤:

Leader activation:选取合适的主服务器,同步数据,然后开始接受客户端事务操作并发起相关提议。

Active messaging:接受客户端的事务操作,然后进行提议,提交等相关操作。

Zookeeper Atomic Broadcast是一个比较完备的协议。我们把系统运行期间处理的所有一系列提议当作一个整体,而不是只关注其中一个提议。严格排序特性可以使得我们的协议更高效、简洁。当Quorum中的服务器(包括主服务器自己)与主服务器的状态保持同步时,主服务器才会成为激活状态。这里的状态是指,所有主服务器认为已经提交的提议,以及主服务器发送出去的要求跟随自己的提议(NEW_LEADER)。当然这只是主服务器假设的,这些状态是否真的都被从服务器同步?答案当然是肯定的,具体原因接下来进行分析。

选主(Leader Activation)

Zookeeper不要求具体的选主算法,只要满足如下两点即可:

  1. 主服务器能看到集群中最大的zxid。
  2. 一半以上的服务器(包括自己)都确认跟随主服务器。

第一点保证主服务器的zxid是集群里最大的,这一点可以保证整个系统的正确性。第二点主要确保系统的可用性。我们会验证第二个特点,在选主过程中或者选主之后如果出现错误,并且主服务器的从服务器丢失之后,我们将会抛弃这一个主服务器,之后进行新一轮的选主过程。

选主之后,只有一个服务器充当主服务器的角色,并且主服务器会等待其他服务器其进行连接。主服务器会通过提议的方式把数据同步到从服务器,如果从服务器落后主服务器太多,主服务器会发送一个完整的快照到从服务器。

如果一个从服务区看到一个提议U,这个U没有被主服务器看到,并且这个U的zxid比主服务器的zxid还要大。那么说明从服务器肯定是在选主之后加入集群的。因为提议肯定是被大多数服务器提交才会变得有效,然而大多数选出来的主服务器竟然没有看到这个U,那就说明这个U提议并不是有效的,所以当从服务器连接到主服务器时,主服务器会通知从服务器丢掉这个提议的相关数据。

主服务器在获得最新的事务id之后,将其中的epoch+1,然后获得的一个新的事务id(zxid),这个事务id会被用来后续的提议。当主服务器与从服务器同步完成之后,它会发送一个NEW_LEADER提议,一旦这个提议被大部分从服务器确认提交,主服务器便开始接收客户端发起的事务操作。

选主过程虽然听起来很复杂,但是,我们可以通过如下几个规则来简单概述:

  1. 从服务器在与主服务器同步完成之后,在接收到NEW_LEADER提议之后,会确认这个提议。
  2. 从服务器只会确认自己曾经投给最大zxid的服务器发来的NEW_LEADER提议。
  3. 当新主服务器收到来自Quorum中的从服务器的确认之后,会执行提交操作。
  4. 当从服务器提交主服务器的NEW_LEADER提议之后,他会提交以后任何来自主服务的数据。
  5. 主服务器的NEW_LEADER提议在没有提交之前是不会接受任何新的提议。

当选主过程中出现错误,主服务器的NEW_LEADER提议不会被Quorum中的从服务器提交,所以等超时后,集群会进行新一轮的选主过程。

处理消息(Active Messaging)

选主过程会执行一些复杂的操作,一旦主服务确定之后,它便开始执行提议的分发流程。只要这个主服务器一直存活,就不会有其他的服务器被选成主服务器,因为它不会获得Quorum中服务器的投票。如果确实出现了一个新的主服务器,那么意味着之前的主服务器失去了Quorum中服务器的投票,新的主服务器会在选主过程中清除一些没有提交的数据。

Zookeeper的消息执行流程类似经典的二阶段提交。

因为所有传输通道都是FIFO,所以所有操作都是有顺序的。

  1. 主服务器向从服务器发送的提议顺序和主服务器接收到的客户端请求顺序是一致的。因为我们使用的是FIFO通道,所以从服务器收到的提议顺序和客户端发送请求的顺序是一致的。
  2. 从服务器按照接收消息的顺序去处理消息。因为使用了FIFO通道所以,从服务器发送确认消息的顺序和主服务器接收到确认消息的顺序是一致的。如果消息m被持久化到磁盘,那么m之前的消息也都会被持久化到磁盘。
  3. 主服务器收到Quorum中服务器的确认后,会向所有的从服务器发送一个COMMIT消息。因为消息确认是有顺序的,所以COMMIT也会按照相同的顺序被从服务器接收。
  4. COMMIT会按照顺序被执行,当从服务器收到COMMIT消息时,会把提议的消息持久化到磁盘。

总结(Summary)

所以主服务器认为被提交的数据,是否真正的都被提交了呢?首先,每一个提议有一个唯一的事务id(zxid),我们不用担心同一个zxid会被多个提议使用,所有的服务器看到的和记录的提议都是有顺序的;提议按照顺序提交;同一时刻只会有一个主服务器,并且从服务器同一时刻只会跟随一个主服务器;主服务器会看到上一个主服务器提交的最后一个提议,因为Quorum中的服务器都已经提交了;任何上一个主服务器没有提交的提议,但是被新的主服务器看到的提议,也会被新的主服务器执行提交操作,然后转成激活状态(注:因为主服务器会在收到Quorum中的服务器确认之后会给客户端返回成功,这时候主服务器发送的提交信息可能还没发送到从服务器,主服务器便宕机了)。

对比(Comparisons)

这不正是Multi-Paxos吗?当然不是,Multi-Paxos假设只有一个协调者。我们不基于这个假设,当主服务器宕机时我们通过选主流程重新确立一个新的主服务器。

这不是Paxos吗?你的处理消息(Active Messaging)看着像Paxos的第二阶段呢?事实上,处理消息(Active Messaging)看着更像是没有弃权流程的二阶段提交。处理消息(Active Messaging)与他们不同的一点是要求所有的提议都是顺序递增的。如果我们不严格地按照FIFO的顺序去处理数据包,那么系统将会垮掉。

一致性保证(Consistency Guarantees)

Zookeeper的一致性保证介于顺序一致性(sequential consistency)和线性一致性(linearizability)之间。这一节我们介绍Zookeeper提供的一致性保证。

Zookeeper的写操作是线性一致性的。换句话说,每个客户端发起的写请求会在Zookeeper那按照接收时间进行排序。但是只讲写操作是线性一致性的没有意义,我们必须同时考虑读操作。

Zookeeper的读操作不是线性一直的,因为他们可能会读取一些老数据。因为客户端进行读取数据时并不是从Quorum中的服务器去读,而只从它所连接的服务器那里读取数据。Zookeeper之所以这么做是因为读取效率要比一致性保证更重要。Zookeeper的读是顺序一致性的,因为读取的顺序和单个客户端写入的顺序是一致的。为了尽可能实现线性一致性读,可以再读操作之前执行sync命令。但是这样也不能保证读取到最新的数据,因为sync并没有多数派性质。假设有个场景,两个服务器都认为自己是主服务器(tcp超时时间比syncLimit * tickTime短),虽然这在实践当中很难出现,但是在讨论理论性的问题时应该记住这个场景。在这个场景中,sync可能被老的主服务器执行,因此客户端可能读取到老的数据。为了提供线性一致性读,在读数据执行必须执行和写操作一样的具有多数派性质的操作。

所以Zookeeper的一致性保证应该叫做ordered sequential consistency 或者 OSC(U),介于顺序一致性和线性一致性之间。

Quorum

原子广播协议和选主过程都是用了Quorum这一个性质来保证系统状态的一致性。默认,Zookeeper使用Majority Quorum,每次进行投票时,都必须获得大多数服务器的投票才算有效。比如从服务器对主服务器提议的确定就是一个大多数投票性质。

大多数理论(Majority)中我们真正需要的一个属性就是,任何两个多数派最少会有一个相交的服务器,我们可以通过这个服务器来确保操作的一致性。当然也有其他形式的Quorum投票形式,区别于Majority,我们可以为某个服务器赋予很高的权重,表明这个服务器的投票可能要比其他服务器的投票重要很多。为了形成一个quorum,我们获得的投票的权重总和必须是所有服务器权重的一半以上。

分层部署方式提供了另一种使用权重的方式。我们把服务器分成互不相交的几组,叫做G,并且为每个服务器都赋予权重。为了形成quorum,必须获得G当中一半以上的组,比如对于G中的每一个组g,从每个g中获得投票的权重数量必须是g中所有服务器权重的一半以上。有意思的是,这样可以获得比Majority quorum更小的quorum。比如,我们有9台服务器,每个服务器的权重都是1,然后分成三组,所以我们可以得到一个有4台服务器的quorum。如下图,分三组,每组三个服务器,所以只要获得两组,然后每组又能获得两个服务器,一共四台服务器就可以形成一个quorum,因为任何两个这种quorum都可以找到一台共有的服务器,但是,如果使用Majority Quorum的话,那么集群的quorum必须是(9/2+1)=5台机器。

1--------------------------------------------+
|                                            |
|    +----+      +----+      +----+          |
|    | 1  |      | 2  |      | 3  |          |
|    +----+      +----+      +----+          |
+--------------------------------------------+
2--------------------------------------------+
|    +----+      +----+      +----+          |
|    | 4  |      | 5  |      | 6  |          |
|    +----+      +----+      +----+          |
|                                            |
+--------------------------------------------+
3--------------------------------------------+
|    +----+      +----+      +----+          |
|    | 7  |      | 8  |      | 9  |          |
|    +----+      +----+      +----+          |
|                                            |
+--------------------------------------------+

Zookeeper提供了majority quorums, weights quorums和hierarchy quorums三种形式的集群配置方式。

翻译自ZooKeeper Internals

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值