Zookeeper概览

前言 Zookeeper 是一个典型的 分布式数据一致性 的解决方案,是谷歌 Chubby 的开源实现,在分布式系统中有非常广泛的应用。
【Zookeeper概览】分布式应用程序可以基于它来实现 数据发布/订阅、分布式协调/通知、集群管理、Master 选举、命名服务、分布式锁和分布式队列 等功能。
在诸如 HDFS、Yarn、HBase、Kafka、Flink 等著名分布式系统中都使用 Zookeeper 来实现各自的 分布式协调机制。
在大数据系统中,Zookeeper 是必不可少的组件,所有大数据平台上必定运行着一组 Zookeeper 服务器集群。
对于大部分业务开发人员来说,Zookeeper 是属于幕后默默干活奉献的角色,在开发过程中接触的往往都是基于 Zookeeper 的上层系统,这使得很多开发人员对 Zookeeper 处于一种 知道它很重要,但是对它缺不熟悉 的状态。
虽然在大部分工作中,开发人员并不需要完全了解 Zookeeper 是如何实现的,但是保持对这个重要组件的了解与探索能够在一定程度上 提升对现有分布式系统实现的理解。
本文将会从 Zookeeper 的 基本概念 开始介绍,随后使用客户端API模拟了一个简单的 Master-Slaves 集群 中主从节点的行为,并在之后讨论到其 内部实现的机制与服务端相关的参数配置,为分布式系统应用揭开最后一层神秘的面纱。
最后我们会列举一些 Zookeeper 集群在业务系统中的 应用场景。
一、基本概念 1.1 构建健壮的分布式系统
要开发实现一个分布式系统并不容易,相对于开发在一台计算机上运行的单个程序,如何让 一个应用中多个独立的程序协同工作 是一件非常困难的事情。
开发这样的应用,很容易让很多开发人员陷入 如何使多个程序协同工作的逻辑 中,最后导致没有时间更好地思考和实现他们自己的应用程序逻辑。又或者开发人员对协同逻辑关注不够,只是用很少的时间开发了一个简单脆弱的主协调器,导致整体服务的不可靠。
Zookeeper 的出现帮开发人员解决了这一难题,让开发人员能够 专注于系统业务功能的开发,而不是陷入分布式环境的细节中不可自拔。
你可以将 Zookeeper 理解为是一种 提供给分布式系统使用的协调工具,给开发人员 提供一套容易理解和开发的接口,从而简化分布式系统构建的任务。所有需要跨多个物理主机,或者独立运行的多个软件组件组成的系统都可以通过使用 Zookeeper 来 简化服务节点之间的交互和协同工作。比如:主节点选举、从节点任务分配、崩溃监测等。
Zookeeper 在为开发人员 屏蔽了分布式系统中的细节问题,最大程度简化系统架构师的工作。
在 Zookeeper 之前,人们通过其他中间件(比如MySQL、Redis等)的辅助也能够实现分布式系统的功能,但是 Zookeeper 出现之后构建分布式系统从未如此简单。
假设现在我们需要实现一个 Master-Slaves 主从结构的集群,这个集群至少能够实现:

  • 多主节点互备,并能够进行主节点选举
  • 从节点崩溃监测
  • 集群节点的任务分配、调度
  • 集群元数据管理
想象一下,如果需要你自己在多台机器上实现这些功能会有多大工作量!
但是别担心, Zookeeper 会给我们提供实现这些功能的工具,在接下来的篇幅相信你能够找到实现方案。
1.2 集群架构
Zookeeper概览
文章图片

如上图所示, Zookeeper 集群中只有两种角色的节点:Server服务端 和 Client客户端。
多台服务端机器组成 Zookeeper 集群,而 HDFS、Yarn、HBase、Kafka、Flink 等系统中则使用 Zookeeper 的客户端来连接服务端并实现自己想要的功能,在 Zookeeper 看来他们都属于客户端节点。
在服务端节点中,还细分为以下三种类型:
  • Leader: Leader 是集群中的的主节点,负责响应 所有对 ZooKeeper 状态变更的请求,写操作都走 Leader。
  • Follower: Follower 负责处理本服务器上的 读请求,对于写请求会转发给 Leader 进行处理。
  • Observer: Observer 作为集群中的 观察节点,本身并 不参加选举也不响应提议、不需要将事务持久化到磁盘,但是可以提供 一定程度的读功能。
服务端上运行的 Zookeeper 进程可能是三种类型的任意一种,谁成为 Leader 谁成为 Follower 是由 选举机制 来决定的,我们会在稍后讨论它。
现在只需要记住, Zookeeper 集群由 服务端和客户端 组成,服务端中有 Leader、Follower、Observer 三种类型的服务各自负责不同的功能即可。
Zookeeper 有两种运行模式:独立模式(Standalone) 和 仲裁模式(Quorum)。
独立模式下, Zookeeper 只有一个服务端,所有客户端都和它交互,这对于一个需要高可用的系统来说是不可接受的。
所以一般情况下我们都会使用仲裁模式,也就是集群模式。
在仲裁模式下,由于有 多个服务端同时为客户端提供服务,那么当客户端发出一个写请求后,服务端之间怎么进行数据同步以保证数据的一致性呢?
  • 如果客户端写入数据后,如果需要所有服务器同步完成才返回则 延迟会非常高,这显然是不可接受的。
  • 反之如果写入一个服务端就返回成功,当该节点宕机就可能造成 数据丢失与服务不可用,这显然也是不能接受的。
仲裁模式下规定,一个客户端的写请求 需要至少 n个服务端完成数据更新的同步才算此次写请求成功完成。
n被称为 有效的服务器数量,那么有效的服务器数量如何确认呢?
Zookeeper 规定有效服务器数量需要 大于集群中一半以上的服务器数量:
  • n <= 一半:可用性太低,集群若挂了一半的服务,则剩下的服务节点可能是未同步状态,整体服务不可用。
  • n > 一半:就算挂了一半的服务器,仍然有 至少一个服务节点数据数据完整的,保证整体服务仍然是可用的,其余节点可以从该节点同步数据。
这种模式可以最大程度保证数据不会因为各种网络问题而丢失。
而且我们建议集群总体数量最好是 奇数:
假设集群共有 f 个节点,则最多能容忍 n 个服务节点宕机,n < f/2。 如果 f 为偶数,比如8,那么能容忍宕机的数量 n 为3(f/2=4,比4小的最大的数为3)。 那么此时,集群中必须要有5个节点确认写入成功,这和 f=7 的集群效果一致,能够容忍的宕机数量 n=3。 也就是说7节点集群的可用性和8节点集群的可用性相同。 而且8节点集群比7节点集群需要多消耗一个同步资源,效率更低。

1.2 Session
客户端向服务端发起连接时,会打开一个长连接的 Session会话,此后双方的所有交互都会基于这个会话通道进行。
一个 Session会话从打开到销毁有一个完整的生命周期,如题下图所示:
Zookeeper概览
文章图片

  1. NOT_CONNECTED: 客户端开始启动创建会话时的状态,该状态只会停留很短的时间,随着连接的初始化快速转移到 CONNECTING状态。
  2. CONNECTING: 客户端尝试与服务端建立连接的状态,连接成功则进入 CONNECTED状态,连接失败则进入 CLOSED状态。
  3. CONNECTED: 客户端成功与服务端建立连接后为 CONNECTED状态,双方可以开始进行交互。如果在此状态中出现服务端宕机或者网络阻塞等问题,则会退回到 CONNECTING尝试连接状态。
  4. CLOSED: 客户端主动关闭会话将从 CONNECTED转移到 CLOSED,如果客户端无法在尝试连接中无法连接上服务端,会话将从 CONNECTED状态进入 CLOSED状态。
和Web网站中间的 Session 一样, Zookeeper 的 Session 也有 会话超时 的概念。
会话成功建立之后,服务端会时刻检查这个会话是否在 时间t之内有活动,如果没有则会判断 该会话超时并注销会话。
时间t称为 会话超时时间,由 maxSessionTimeout参数定义,我们会在后面讨论它。
客户端会在 1/3t的时间点主动发送心跳到服务端以维持会话。
如果在 2/3t时间内客户端没有收到响应(心跳无法到达该服务端,或者该服务端无法响应),此时离会话超时还有 1/3t的时间,那么客户端会去寻找其他服务端节点以求在会话超时之前找到一个可靠的服务端。
Zookeeper概览
文章图片

上图展示了一个客户端重连的过程:
  1. 客户端连接到服务端S1
  2. 客户端执行一个创建节点的操作,服务端执行成功后将会返回 zxid=1
  3. 客户端到服务端S1的连接丢失
  4. 客户端提供的 Zookeeper 服务列表连接尝试连接到服务端S2,此时S2没有zxid,放弃此连接
  5. 客户端尝试连接到服务端S4,此时S4 zxid=2,大于客户端zxid,可以建立连接
客户端的每次写操作服务端都会返回类似 递增的zxid,客户端选择连接时只连接到 服务端zxid >= 客户端zxid 的节点,否则说明 该服务器还没有同步状态数据。
1.3 znode节点
Zookeeper 通过 协同数据 的管理来完成多个节点协同工作的任务。
需要注意的是, Zookeeper 管理的协同数据是元数据而不是应用数据。比如在博客网站上发表一篇博客文章的过程中,博客文章是应用数据,而博客文章存储在哪个服务器就属于元数据/协同数据。
在 Zookeeper 服务端的内部,所有数据以 Znode Tree 的树结构 形式组织,树中的每个节点称为 znode节点,如下图所示:
Zookeeper概览
文章图片

每个znode节点在分布式应用中都有其特定的含义:
  • 没有数据的 /master节点,本身就是一个重要的信息,代表master节点还没有选举出
  • /workers节点下的所有子节点代表集群中的 Slaves,节点中的数据表示其ip地址
  • /tasks节点下的所有子节点代表集群中需要被执行的任务信息,节点中的数据表示任务内容
  • /assign节点下的所有子节点表示集群中的哪个子节点正在执行哪个任务,当主节点为从节点分配一个任务时就会在 /assign下新建子节点
znode有四种类型:持久节点(persistent)、持久有序节点、临时节点(ephemeral)和临时有序节点。
  • 持久节点: 创建之后需要主动delete删除,否则会一直存在,比如集群中的任务分配信息,及时主节点崩溃了也需要保留。
  • 临时节点: 会在session关闭时自动删除,临时节点没有子节点,比如集群主节点创建的临时节点消失,代表主节点宕机需要重新选举主节点。
  • 有序节点: 会自动在路径后添加递增的序号,可以直观的看到节点的创建顺序,可以用于任务序号标记等。
基于这些节点和数据,客户端为开发人员提供了一组 API 进行操作:
//创建节点 create /zk_test //创建带数据的节点 create /zk_test data //创建临时节点 create -e /zk_test //创建带数据的临时节点 create -e /zk_test data//设置节点携带的数据 setData /zk_test data //获取节点携带的数据 getData /zk_test //创建子节点 create /zk_test/children //获取节点的子节点 getChildren /zk_test//判断节点是否存在 exists /zk_test //删除节点 delete /zk_test

znode还可以在 setDatadelete时带上版本号信息,在多客户端并行操作时,如果版本号和传入的不一致则操作失败。
1.4 监视与通知
当 Zookeeper 服务端节点的数据发生了变化,客户端需要如何感知?
传统的做法是使用 轮询,客户端不停地向服务端发起查询请求以获得最新的数据,但是这种方式往往是 十分低效且浪费带宽资源 的,因为谁都不知道服务端的数据什么时候会更新。
为了替换轮询, Zookeeper 使用了 基于通知 的机制:客户端在服务端感兴趣的节点上设置 监视点(Watcher),当该节点发生变化时由服务端通知客户端。
需要注意的是,监视点是一个 单次触发 的动作,触发通知之后客户端需要重新设置监视点才能获得下一次的通知。
客户端可以使用监视点在服务端设置诸如 znode的数据变化、znode的子节点变化、znode的创建和删除 等事件的监控。
监视点的生命周期存在会话中,所以会随着会话销毁而销毁。
值得注意的是,即使客户端与服务端临时断开连接后 重连至另外的服务端,客户端之前设置的监视点仍然有效。
客户端重连时会将注册的监视点重新发送给新的服务端,服务端会一一注册并检查其关心的节点是否发生变化,如有变化则发送通知给客户端。
二、客户端实践 2.1 同步API与异步API
Zookeeper 分别为开发人员提供了 同步API和异步API 使用。
在同步API中开发人员需要自己通过 while循环来控制客户端与服务端的交互,并在交互过程中需要处理诸如 ConnectionLossException、NodeExistsException等异常,否则客户端将 无法完整的获得 服务端传递的信息。
而在异步API中,客户端在与服务端交互过程中通过设置 回调对象 的方式来提供回调方法。
部分回调接口定义如下:
public interface StringCallback { //rc: 调用的结果值,返回OK或者与KeeperException对应的异常编码 //path: 节点路径 //ctx: 上下文参数 //name: 节点名称 void processResult(int rc, String path, Object ctx, String name); }public interface StatCallback() { //stat: 服务端返回的相关信息,如zxid时间戳、子节点个数等元数据 void processResult(int rc, String path, Object ctx, Stat stat); }public interface DataCallback() { //data: 设置的节点数据 void processResult(int rc, String path, Object ctx, byte[] data, Stat stat); }

服务端收到客户端请求(如 create)中携带的回调对象后,会将 需要响应的信息写入该对象中并回传给客户端的回调线程处理,回调线程中会自动调用开发人员设置的 processResult回调方法。
如此一来,开发人员只需要在回调方法中 根据服务端的返回编码 做出对应的处理即可。在保证编程简洁的同时,效率也比同步API要高得多。
接下来,我们使用异步API来模拟实现一个 Master-Slaves 架构的分布式应用中,主节点和从节点与 Zookeeper 服务端的交互。
2.2 模拟主节点
为了实现与服务端的交互,我们首先需要初始化一个 Zookeeper 客户端:
//接口定义 //connectString:Zookeeper 集群连接字符串,多个地址逗号隔开 //sessionTimeout: 会话超时时间 //watcher: 监视点对象 Zookeeper(String connectString, int sessionTimeout, Watcher watcher)//实例化客户端对象 ZooKeeper zk = new ZooKeeper (hostPort, 15000, null);

实例化完客户端对象后,我们定义一个 startMaster方法,用来启动一个客户端并在服务端注册 临时的 /master节点,以表示它是系统中的主节点。
String serverId = Integer.toHexString(random.nextInt()); void startMaster() { zk.create("/master", //节点数据只能存储字节数组 serverId.getBytes(), //权限设置 Ids.OPEN_ACL_UNSAFE, //创建临时节点 CreateMode.EPHEMERAL, //设置回调对象 masterCreateCallback, //当前不设置监视点 null); }

masterCreateCallback是一个回调对象,在这里面我们定义了服务端接收 create请求并返回后,客户端 需要处理的事情。
boolean isLeader = false; StringCallback masterCreateCallback = new StringCallback() { void processResult(int rc, String path, Object ctx, String name) { //获取返回编码 switch(Code.get(rc)) { //连接丢失则继续请求 case CONNECTIONLOSS: checkMaster(); break; //code=0,创建成功 case OK: isLeader = true; break; default: isLeader = false; } System.out.println("I'm" + (isLeader ? "" : "not") + " the leader."); } }

回调结果会有专门的 回调线程 来处理,为了保证顺序性,回调线程只有一个,所以在回调方法中应该尽快完成操作。
如果服务端的响应编码为 CONNECTIONLOSS则表示连接丢失,在该情况下客户端应该 继续请求服务端,否则无法得知 请求是否成功,我们通过 checkMaster方法来处理这个逻辑:
void checkMaster() { zk.getData("/master", //第二个参数为false,表示不设置监视点 false, //设置masterCheckCallback回调对象 masterCheckCallback, null); }DataCallback masterCheckCallback = new DataCallback() { void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) { switch(Code.get(rc)) { //如果为连接丢失则继续请求 case CONNECTIONLOSS: checkMaster(); return; //没有该节点则表示之前的请求没有被处理,重新请求创建节点 case NoNode: startMaster(); return; } } }

以上代码就模拟了一个系统主节点启动时和 Zookeeper 服务端的简单交互。
checkMaster中我们使用 zk.getData时并没有设置任何监视点,此时如果系统主节点宕机退出,/master节点将会消失将会造成应用系统不可用。
前面我们讨论过,这种情况可以通过 设置监视点 来使客户端得到通知,现在我们还是使用 zk.getData方法来演示监视点的设置。
getData方法设置监视点的定义如下:
//手动设置watcher对象 public byte[] getData(final String Path, Watcher watcher, Stat stat) //watch设置为true则使用默认的watcher对象 public byte[] getData(final String Path, boolean watch, Stat stat)

Demo代码:
void checkMaster() { zk.getData("/master", //设置监视点对象 masterDownWatcher, masterCheckCallback, null); }//定义监视点对象 Watcher masterDownWatcher = new Watcher() { public void process (WatchedEvent e) { if (e.getType() == EventType.NodeDeleted) { assert "/master".equals(e.getPath()); //master临时节点删除后,当前客户端重新启动master节点 startMaster(); } } }

WatchedEvent中包含了znode节点事件的定义,开发人员可以通过它捕获到 服务端znode发生的任何变化 来判断客户端需要采取什么行动。
监视点有两种类型:数据监视点、子节点监视点。
  • 数据监视点
    • 设置:exists、getData
    • 触发:创建、删除、设置节点数据时将会触发
  • 子节点监视点
    • 设置:getChildren
    • 触发:子节点创建或者删除时触发
监视点完整的定义与类型如下:
Zookeeper概览
文章图片

2.3 模拟从节点
同样,我们需要在从节点客户端初始化 Zookeeper 对象:
ZooKeeper zk = new ZooKeeper(hostPort, 15000, null);

定义 startWorker方法向服务端 /workers节点注册 临时子节点,并设置值为 Idle表示当前节点空闲:
void startWorker() { zk.create("/workers/worker-" + serverId, "Idle".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL, createWorkerCallback, null); }

createWorkerCallback回调对象中定义了应对服务端响应编码的处理:
StringCallback createWorkerCallback = new StringCallback() { public void processResult(int rc, String path, Object ctx, String name) { switch (Code.get(rc)) { //连接丢失则继续请求创建 case CONNECTIONLOSS: startWorker(); break; case OK: LOG.info("Registered successfully: " + serverId); break; case NODEEXISTS: LOG.warn("Already registered: " + serverId) ; break; default: LOG.error("Something went wrong: " + KeeperException.create(Code.get(rc), path)); } } }

三、内部原理 如果你之前简单了解过 Zookeeper ,那么你肯定听说过 Zookeeper 强悍的性能, Zookeeper 在提供高性能的同时还有高可靠的容错机制。
接下来我们将会详细讨论 Zookeeper 服务端内部 Leader 选举、状态广播更新 等这些机制是如何运行的,以帮助我们更好地理解 Zookeeper 的使用。
3.1 Leader 选举
Zookeeper 服务器首次启动后会进入looking状态,并尝试连接到其他服务器。
此时如果集群中已有 Leader 存在,那么新服务器将会被告知 Leader 地址,并 从 Leader 同步状态信息 以保持数据一致为客户端提供读请求服务。
如果集群中所有服务器都处于looking状态,则表示当前集群中没有leader。
各服务器将会交换 投票信息 进行选举,选举成功后 Leader 节点进入leading状态,其他进入following状态称为 Follower。
集群中的各个服务器通过互相发送 选举通知(leader election notifications) 来交换各自的 投票信息(vote)。
投票信息包含两个内容:
  • voteSid: 服务器标识符
  • voteZxid: 最新的事务zxid信息
一个服务器发出的投票信息类似 (2,3),表示该服务器sid为2,事务zxid为3。
当服务器接收到别人的投票信息时,将会根据以下规则修改自己的投票信息:
  1. 如果收到的投票信息和自己的投票信息满足 if(voteZxid > myZxid || (voteZxid == myZxid && voteSid > mySid)) 条件则修改自己的投票信息修改为收到的投票信息
  2. 否则保留自己的投票信息
当一个服务器收到达到 仲裁数量的一致投票信息,则告诉其他服务器它将 推举票选服务器成为 Leader,如果 一致投票信息的数量不够 或者 投票未达成一致,则将会 重新发送第二轮投票信息。
当 Leader 候选人出现后,只要服务器再收到 仲裁数量的一致推举信息,则表示选举成功,推举大家认同的服务器作为 Leader。
从条件公式中不难看出,拥有最新事务zxid的服务器会成功当选,如果有多个服务器状态都是最新的,那么 id最大 的服务器会成功当选(此时谁做 Leader 都不会影响服务)。
如果当选的 Leader 为自己,则当前服务器会开始行使 Leader 的角色,否则尝试连接到 Leader 进行状态同步,同步完成后才可接受请求。
如果集群中某一台服务器因为网络或者其他原因导致其只 收到了一个较小的zxid值并认其为 Leader,此时它的选举结果和集群中 其他大部分服务器的选举结果就会不一致。
这种情况下,集群中其他大部分服务器会帮它 纠正这个错误,当它意识到集群中有 达到仲裁数量的服务器 都认同一个 Leader 时,它将会重新修改自己的选项。
3.2 高性能的读请求
Zookeeper 服务端会在本地直接处理 读请求(exists、getData、getChildren),读取状态信息并直接返回给客户端。
所以对于 高并发的读负载, Zookeeper 的性能表现非常出色,且 Zookeeper 集群中的所有服务端都能同时为客户端提供 本地读处理。
而对于会改变 Zookeeper 服务状态的 写请求(create、delete、setData),集群中的 Follower 服务器会将该请求 转发给 Leader 处理。
3.3 Zab协议与写请求
Leader 收到写请求后,会进行具有 原子性的事务更新操作,每个事务操作都会由 Leader 赋予其一个 zxid标识符。
Zab 全称 ZooKeeper原子广播协议(ZooKeeper Atomic Broadcast protocal), Zookeeper 使用该协议来完成 事务的广播更新。
通过该协议提交并确认一个事务非常简单,类似 一个两阶段提交 的过程。
follower收到写请求后转发给leader:
  1. leader将 试探性的执行该请求 并将执行结果 以事务的方式对状态更新进行广播
  2. 向所有follower发送一个 状态更新的PROPOSAL提议消息p
  3. follower接收到p后将该提议中的 zxid事务信息写入磁盘的事务日志中,随后向leader发出ack消息
  4. leader统计ack回访的消息数量,如果 达到仲裁数量的确认完成消息则提议通过,发送消息 通知follower继续执行commit操作
  5. follower收到commit消息后会 将事务应用到内存数据中,最后再将结果响应给客户端
Zab 协议保证了所有服务器上 事务被执行的数量和顺序都是一致的。
follower进行事务提交时将更新到数据树上(DataTree),数据数将会定时快照备份到磁盘上(异步),通过加载快照与重放日志达到恢复的效果。
所以follower的事务写操作需要 在独立的磁盘中保证性能与稳定。
因此我们可以看到,写操作的吞吐率取决于仲裁数量的大小,这也就是为什么我们不推荐 Zookeeper 集群数量太多庞大的原因。
四、参数配置 Zookeeper 集群的安装部署十分简单,但是有些参数需要在安装过程中根据自身业务和硬件情况进行调整。
接下来我们会介绍一些基本配置、网络配置和集群配置方面的参数,为 Zookeeper 集群安装设置提供一些指引。
4.1 基本配置
dataDir
  • 配置内存中数据快照的目录,由 异步线程后台写入
  • 对性能不会有太大影响,但是建议配置在空间足够大的盘中
dataLogDir
  • 写请求的事务日志信息,同步顺序写入
  • 最好使用独立且高性能的存储
tickTime
  • 基本实践度量单位,默认客户端会话超时时间为 tick * 2
  • 调整该值将影响 sessionTimeout、initLimit、syncLimit等多个关联配置
  • 值越小越早发现超时问题,但是也有 更高的心跳网络流程与更多的CPU使用率
4.2 网络配置
globalOutstandingLimit
  • 服务器 最多能处理的客户端请求数
  • 为了防止客户端请求量太大,服务端将请求顺序队列存储造成的内存溢出
  • 达到上限后将 不再处理客户端的请求(没有等待请求的客户端可以继续处理)
  • 根据服务器的硬件配置合理设置
maxClientCnxns
  • 限制客户端 单IP最大的并发请求量
  • 可以避免DoS攻击,暴露在外网的集群建议配置
minSessionTimeout
  • 默认为 tickTime * 2
  • 客户端会话超时的 最小时间
maxSessionTimeout
  • 默认为 tickTime * 20
  • 客户端会话超时的 最大时间
4.3 集群配置
initLimit
  • 某follower初次连接到leader后进行 初始化的超时时间
  • 单位为tickTime的倍数
  • 初次连接需要同步和请求大量数据
syncLimit
  • follower与leader进行同步的超时时间,follower超时后将会被移除
  • 单位为tickTime的倍数
leaderServes
  • leader是否为客户端提供服务,默认为yes
  • 集群同步资源消耗大的情况下可关闭
peerType=observer
  • 设置节点类型为观察者
  • 适当添加观察者可以一定程度上增加集群的读负载
五、应用场景 5.1 数据发布/订阅(配置中心)
发布者将数据发布到 Zookeeper 的一个或一系列节点上,供订阅者进行数据订阅进而达到动态获取数据的目的,实现配置信息的 集中式管理和数据的动态更新。
  • 客户端向服务端注册自己需要关注的节点Watcher事件
  • 一旦该节点数据发生变更(通常由开发运维人员操作)
  • 服务端就会向相应的客户端发送Watcher事件通知
  • 客户端接收到这个消息之后,需要主动的到服务端获取最新的数据
Zookeeper概览
文章图片

5.2 命名服务
在分布式系统中,被命名的实体通常可以是 集群中的机器、提供的服务地址或远程对象 等。
其中较为常见的就是一些RPC框架中的服务地址列表,通过使用命名服务客户端应用能够根据指定的名字来 获取资源的实体、服务地址和提供者的信息 等。
比如 Dubbo 的注册中心:
Zookeeper概览
文章图片

5.3 分布式锁
分布式锁是控制分布式系统之间 同步访问共享资源 的一种方式。
Zookeeper 可以很好的实现这种场景,如下是 Zookeeper 对 排他锁和共享锁 的实现。
Zookeeper概览
文章图片

5.4 集群管理与协调
Zookeeper 集群管理与协调的功能在很多被广泛应用的分布式系统中使用,HDFS、Yarn、HBase、Kafka、Flink 等都是 Zookeeper 的客户端,相信很多朋友在学习这些系统的时候总能看到关于 Zookeeper 在其中的应用和实现,这里就不过多累述。

    推荐阅读