Zookeeper 入门系列三 -- Zookeeper 工作原理

1 Zookeeper 的数据复制

Zookeeper 作为一个集群提供一致的数据服务,自然,它要在所有机器间做数据复制。其优点有:

  1. 容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作
  2. 提高系统的扩展能力:把负载分布到多个节点上,或者增加节点来提高系统的负载能力
  3. 提高性能:让客户端本地访问就近的节点,提高用户访问速度

一般来说,从客户端读写访问的透明度来看,数据复制集群系统分下面两种:

  1. 写主:对数据的修改提交给指定的节点。读无此限制,可以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读写分离;
  2. 写任意:对数据的修改可提交给任意的节点,跟读一样。这种情况下,客户端对集群节点的角色与变化透明。

对 Zookeeper 来说,它采用的方式是写任意。通过增加机器,它的读吞吐能力和响应能力扩展性非常好,而写,随着机器的增多吞吐能力肯定下降,而响应能力则取决于具体实现方式,是延迟复制保持最终一致性,还是立即复制快速响应。

至于如何保证数据在集群所有机器的一致性,我们得首先介绍 Paxos 算法。

2 Paxos 算法

为了实现集群的高可用性,用户的数据往往要多重备份,多个副本虽然避免了单点故障,但同时也引入了新的挑战,那就是一致性问题。

Paxos 算法是用来解决分布式系统中,如何就某个值达成一致的算法。

详细了解 Paxos 算法可以参考这篇博客 分布式理论:深入浅出Paxos算法

Paxos 算法通过投票来对写操作进行全局编号,同一时刻,只有一个写操作被批准,同时并发的写操作要去争取选票,只有获得过半数选票的写操作才会被批准(所以永远只会有一个写操作得到批准),其他的写操作竞争失败只好再发起一轮投票,就这样,所有写操作都被严格编号排序,编号严格递增。当一个节点接受了一个编号为100的写操作,之后又接受到编号为99的写操作(因为网络延迟等很多不可预见原因),它马上能意识到自己数据不一致了,自动停止对外服务并重启同步过程。任何一个节点挂掉都不会影响整个集群的数据一致性(总 2n+1 台,除非挂掉大于 n 台)。

3 ZooKeeper 的工作原理

Zookeeper 的核心是原子广播,这个机制保证了各个 Server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 Server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 Server 具有相同的系统状态。

为了保证事务的顺序一致性,ZooKeeper 采用了递增的事务 id 号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了 zxid。实现中 zxid 是一个64位的数字,它高32位是 epoch 用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一个新的 epoch,标识当前属于那个 leader 的统治时期。低32位用于递增计数。

每个 Server 在工作过程中有三种状态:

  1. LOOKING:当前 Server 不知道 leader 是谁,正在搜寻
  2. LEADING:当前 Server 即为选举出来的 leader
  3. FOLLOWING:leader 已经选举出来,当前 Server 与之同步

4 选主流程

我们先来明确 Zookeeper 角色。
在这里插入图片描述
当 leader 崩溃或者 leader 失去大多数的 follower,这时候 ZooKeeper 进入恢复模式,恢复模式需要重新选举出一个新的 leader,让所有的 Server 都恢复到一个正确的状态。ZooKeeper 的选举算法有两种:一种是基于 basic paxos 实现的,另外一种是基于 fast paxos 算法实现的。系统默认的选举算法为 fast paxos。先介绍 basic paxos 流程:

  1. 选举线程由当前 Server 发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的 Server;
  2. 选举线程首先向所有 Server 发起一次询问(包括自己);
  3. 选举线程收到回复后,验证是否是自己发起的询问(验证 zxid 是否一致),然后获取对方 id(myid),并存储到当前询问对象列表中,最后获取对方提议的 leader 相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
  4. 收到所有 Server 回复以后,就计算出 zxid 最大的那个 Server,并将这个 Server 相关信息设置成下一次要投票的 Server;
  5. 线程将当前 zxid 最大的 Server 设置为当前 Server 要推荐的 Leader,如果此时获胜的 Server 获得 n/2 + 1 的 Server 票数, 设置当前推荐的 leader 为获胜的 Server,将根据获胜的 Server 相关信息设置自己的状态,否则,继续这个过程,直到 leader 被选举出来。

通过流程分析我们可以得出:要使 Leader 获得多数 Server 的支持,则 Server 总数必须是奇数 2n+1,且存活的 Server 的数目不得少于 n+1

每个 Server 启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的 server 还会从磁盘快照中恢复数据和会话信息,ZooKeeper 会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。选主的具体流程图如下所示:
在这里插入图片描述
fast paxos 流程是在选举过程中,某 Server 首先向所有 Server 提议自己要成为 leader,当其它 Server 收到提议以后,解决 epoch 和 zxid 的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出 Leader。其流程图如下所示:
在这里插入图片描述

5 同步流程

选完 leader 以后,ZooKeeper 就进入状态同步过程。

  1. leader 等待 server 连接;
  2. Follower 连接 leader,将最大的 zxid 发送给 leader;
  3. Leader 根据 follower 的 zxid 确定同步点;
  4. 完成同步后通知 follower 已经成为 uptodate 状态;
  5. Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。

流程图如下所示:
在这里插入图片描述

6 工作流程

6.1 Leader 工作流程

Leader 主要有三个功能:

  1. 恢复数据;
  2. 维持与 Learner 的心跳,接收 Learner 请求并判断 Learner 的请求消息类型;
  3. Learner 的消息类型主要有 PING 消息、REQUEST 消息、ACK 消息、REVALIDATE 消息,根据不同的消息类型,进行不同的处理。

PING 消息是指 Learner 的心跳信息;REQUEST 消息是 Follower 发送的提议信息,包括写请求及同步请求;ACK 消息是 Follower 的对提议的回复,超过半数的 Follower 通过,则 commit 该提议;REVALIDATE 消息是用来延长 SESSION 有效时间。

Leader 的工作流程简图如下所示,在实际实现中,流程要比下图复杂得多,启动了三个线程来实现功能。
在这里插入图片描述

6.2 Follower 工作流程

Follower 主要有四个功能:

  1. 向 Leader 发送请求(PING 消息、REQUEST 消息、ACK 消息、REVALIDATE 消息);
  2. 接收 Leader 消息并进行处理;
  3. 接收 Client 的请求,如果为写请求,发送给 Leader 进行投票;
  4. 返回 Client 结果。

Follower 的消息循环处理如下几种来自 Leader 的消息:

  1. PING 消息: 心跳消息;
  2. PROPOSAL 消息:Leader 发起的提案,要求 Follower 投票;
  3. COMMIT 消息:服务器端最新一次提案的信息;
  4. UPTODATE 消息:表明同步完成;
  5. REVALIDATE 消息:根据 Leader 的 REVALIDATE 结果,关闭待 revalidate 的 session 还是允许其接受消息;
  6. SYNC 消息:返回 SYNC 结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。

Follower 的工作流程简图如下所示,在实际实现中,Follower 是通过5个线程来实现功能的。
在这里插入图片描述

observer 工作流程

observer 与 Follower 大致相同,唯一不同的地方就是 observer 不会参加 leader 发起的投票。

7 路由和负载均衡

当服务越来越多,规模越来越大时,对应的机器数量也越来越庞大,单靠人工来管理和维护服务及地址的配置信息,已经越来越困难。并且,依赖单一的硬件负载均衡设备或者使用 LVS、Nginx 等软件方案进行路由和负载均衡调度,单点故障的问题也开始凸显,一旦服务路由或者负载均衡服务器宕机,依赖其的所有服务均将失效。如果采用双机高可用的部署方案,使用一台服务器 “stand by”,能部分解决问题,但是鉴于负载均衡设备的昂贵成本,已难以全面推广。

一旦服务器与 ZooKeeper 集群断开连接,节点也就不存在了,通过注册相应的 watcher,服务消费者能够第一时间获知服务提供者机器信息的变更。利用其 znode 的特点和 watcher 机制,将其作为动态注册和获取服务信息的配置中心,统一管理服务名称和其对应的服务器列表信息,我们能够近乎实时地感知到后端服务器的状态(上线、下线、宕机)。Zookeeper 集群间通过 Zab 协议,服务配置信息能够保持一致,而 Zookeeper 本身容错特性和 leader 选举机制,能保证我们方便地进行扩容。

Zookeeper 中,服务提供者在启动时,将其提供的服务名称、服务器地址、以节点的形式注册到服务配置中心,服务消费者通过服务配置中心来获得需要调用的服务名称节点下的机器列表节点。通过一定的负载均衡算法,选取其中一台服务器进行调用。当服务器宕机或者下线时,由于 znode 非持久的特性,相应的机器可以动态地从服务配置中心里面移除,并触发服务消费者的 watcher。在这个过程中,服务消费者只有在第一次调用服务时需要查询服务配置中心,然后将查询到的服务信息缓存到本地,后面的调用直接使用本地缓存的服务地址列表信息,而不需要重新发起请求到服务配置中心去获取相应的服务地址列表,直到服务的地址列表有变更(机器上线或者下线),变更行为会触发服务消费者注册的相应的 watcher 进行服务地址的重新查询。这种无中心化的结构,使得服务消费者在服务信息没有变更时,几乎不依赖配置中心,解决了之前负载均衡设备所导致的单点故障的问题,并且大大降低了服务配置中心的压力。

通过 Zookeeper 来实现服务动态注册、机器上线与下线的动态感知,扩容方便,容错性好,且无中心化结构能够解决之前使用负载均衡设备所带来的单点故障问题。只有当配置信息更新时服务消费者才会去 Zookeeper 上获取最新的服务地址列表,其他时候使用本地缓存即可,这样服务消费者在服务信息没有变更时,几乎不依赖配置中心,能大大降低配置中心的压力。

通俗的说,就是把 ZooKeeper 作为一个服务的注册中心,在其中登记每个服务,每台服务器知道自己是属于哪个服务,在服务器启动时,自己向所属服务进行登记,这样,一个树形的服务结构就呈现出来了。服务的调用者需要到注册中心里面查找能提供所需服务的服务器列表,然后自己根据负载均衡算法,从中选取一台服务器进行连接。调用者取到服务器列表后,就可以缓存到自己内部,省得下次再取,当服务器列表发生变化,例如某台服务器宕机下线,或者新加了服务器,ZooKeeper 会自动通知调用者重新获取服务器列表。由于 ZooKeeper 并没有内置负载均衡策略,需要调用者自己实现,这个方案只是利用了 ZooKeeper 的树形数据结构、watcher 机制等特性,把 ZooKeeper 作为服务的注册和变更通知中心。

8 Zookeeper 特性

  1. 最终一致性:client 不论连接到哪个 Server,展示给它都是同一个视图,这是 Zookeeper 最重要的性能。
  2. 可靠性:具有简单、健壮、良好的性能,如果消息被到一台服务器接受,那么它将被所有的服务器接受。
  3. 实时性:Zookeeper 保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper 不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用 sync() 接口。
  4. 等待无关(wait-free):慢的或者失效的 client 不得干预快速的 client 的请求,使得每个 client 都能有效的等待。
  5. 原子性:更新只能成功或者失败,没有中间状态。
  6. 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有 Server 上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。

参考:zookeeper的原理和应用(非常详细透彻)
zookeeper工作原理、核心机制
Nginx/ZooKeeper 负载均衡的差异

发布了127 篇原创文章 · 获赞 237 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Geffin/article/details/103996325