zookeeper总结

一、zookeeper简介

ZooKeeper的设计更专注于任务协作,并不提供任何锁的接口或通用存储数据接口。ZooKeeper服务所管理的是分布式服务间的协同数据,实现通用的协作任 务,包括选举主节点、管理组内成员关系、管理元数据等。ZooKeeper不适合用作海量数据存储,不同的应用有不同的需求,如对一致性和持久性的不同需求,应该将应用数据和协同数据独立开。

使用 ZooKeeper可以让开发人员更专注于其应用本身的逻辑而不是神秘的分布式系统概念。通过ZooKeeper的客户 端API连接到ZooKeeper服务器端进行相应的操作。Zookeep的客户端 API功能强大,其中包括:

  • ·保障强一致性、有序性和持久性。
  • ·实现通用的同步原语的能力。
  • ·在实际分布式系统中,并发往往导致不正确的行为。ZooKeeper 提供了一种简单的并发处理机制。

1.1 zookeeper构建分布式系统

 分布式系统中的进程通信有两种选择:直接通过网络进行信息交 换,或读写某些共享存储。ZooKeeper使用共享存储模型来实现应用间 的协作和同步原语。对于共享存储本身,又需要在进程和存储间进行网 络通信。网络通信是分布式系统中并发设计的基础。

在真实的系统中,我们需要特别注意以下问题:消息延迟、处理器性能、时钟偏移,造成我们无法判断是进程崩溃还是某些因素导致了延时。ZooKeeper的精确设计简化了这些问题的处理,ZooKeeper并不是完 全消除这些问题,而是将这些问题在应用服务层面上完全透明化,使得 这些问题更容易处理。

分布式系统希望满足CAP定律,示一致性 (Consistency)、可用性(Availability)和分区容错性(Partitiontolerance)。

1.2 主-从应用

在这种架构中,主节点进程负责跟踪从节点状态和任务的有效 性,并分配任务到从节点。这种模式的系统,有三个关键问题要解决:主节点失效、从节点失效、通信故障。针对这些问题,主从架构需要满足:

  • 主节点选举:使得主节点可以给从节点分配任务。
  • 崩溃检测:主节点必须具有检测从节点崩溃或失去连接的能力。
  • 组成员关系管理:主节点必须具有知道哪一个从节点可以执行任务的能力。
  • 元数据管理:主节点和从节点必须具有通过某种可靠的方式来保存分配状态和执 行状态的能力。

1.2.1 主节点失效

需要一个备份主节点,恢复主节点崩溃时的状态。状态恢复并不是唯一的重要问题。假如主节点有效,备份主节点却 认为主节点已经崩溃。这种错误的假设可能发生在以下情况,例如主节 点负载很高,导致消息任意延迟。这时可能出现两个或多个主节点独立工作,一般称之为“脑裂”,造成数据的不一致。

二、zookeeper基础

一个znode,没有数据常常表达了重要的信息。比如,在主-从 模式的例子中,主节点的znode没有数据,表示当前还没有选举出主节点。

·/workers节点作为父节点,其下每个znode子节点保存了系统中一 个可用从节点信息。如图2-1所示,有一个从节点(foot.com:2181)。

·/tasks节点作为父节点,其下每个znode子节点保存了所有已经创 建并等待从节点执行的任务的信息,主-从模式的应用的客户端在/tasks 下添加一个znode子节点,用来表示一个新任务,并等待任务状态的 znode节点。

·/assign节点作为父节点,其下每个znode子节点保存了分配到某个 从节点的一个任务信息,当主节点为某个从节点分配了一个任务,就会 在/assign下增加一个子节点。

2.1API概述

ZooKeeper的API暴露了以下方法:

  • create/path data 创建一个名为/path的znode节点,并包含数据data。
  • delete/path 删除名为/path的znode。
  • exists/path 检查是否存在名为/path的节点。
  • setData/path data 设置名为/path的znode的数据为data。
  • getData/path 返回名为/path节点的数据信息。
  • getChildren/path 返回所有/path节点的所有子节点列表

    设置一个znode节点的数据或读取时,znode节点的内容会被整个 替换或全部读取进来。

2.1.1节点类型

znode一共有4种类型:持久的(persistent)、临时的 (ephemeral)、持久有序的(persistent_sequential)和临时有序的 (ephemeral_sequential)。

一个临时znode,在以下两种情况下将会被删除: 1.当创建该znode的客户端的会话因超时或主动关闭而中止时。 2.当某个客户端(不一定是创建者)主动删除该节点时。

一个有序znode节 点被分配唯一个单调递增的整数。当创建有序节点时,一个序号会被追 加到路径之后。有序znode通过 提供了创建具有唯一名称的znode的简单方式。同时也通过这种方式可 以直观地查看znode的创建顺序。

2.1.2 监视与通知

zookeeper使用通知机制来获取节点和节点数据的变化,客户端向ZooKeeper注册需要接收通知的 znode,通过对znode设置监视点(watch)来接收通知。监视点是一个单 次触发的操作,意即监视点会触发一个通知。为了接收多个通知,客户 端必须在每次通知后设置一个新的监视点。新的监视点设置之前,客户端会先读取zookeeper的状态,避免错过设置监视点前的状态变化。

2.1.3 版本

zookeeper使用版本来阻止并行操作的不一致性, 每一个znode都有一个版本号,它随着每次数据变化而自增。两个 API操作可以有条件地执行:setData和delete。这两个调用以版本号作为传入参数,只有当传入参数的版本号与服务器上的版本号一致时调用才 会成功。

2.2 zookeeper架构

ZooKeeper服务器端运行于两种模式下:独立模式(standalone)和 仲裁模式(quorum)。独立模式几乎与其术语所描述的一样:有一个单 独的服务器,ZooKeeper状态无法复制。在仲裁模式下,具有一组 ZooKeeper服务器,我们称为ZooKeeper集合(ZooKeeper ensemble), 它们之前可以进行状态的复制,并同时为服务于客户端的请求。

2.2.1 ZooKeeper仲裁

仲裁模式下,ZooKeeper复制集群中的所有服务器的数据树。zookeeper为了避免延迟,只要设定的最小个数的服务器完成了数据的保存,就会允许客户端继续进行操作,剩余的服务器也最终也将捕获到数据并保存。法定人数必须大于数量的一半。

2.2.2 会话

对ZooKeeper集合执行任何请求前,一个客户端必须先与服务建 立会话。会话的概念非常重要,对ZooKeeper的运行也非常关键。客户 端提交给ZooKeeper的所有操作均关联在一个会话上。

会话提供了顺序保障,这就意味着同一个会话中的请求会以 FIFO(先进先出)顺序执行。如果客户端拥有多个并发的会话或者连贯(会话失效后再次创建)的会话,将无法保证FIFO顺序。

2.3 zookeeper使用

2.3.1 运行zookeeper

http://zookeeper.apache.org下载zookeeper压缩包,解压后运行。bin/zkServer.sh start

2.3.2 会话的状态和声明周期

会话的生命周期(lifetime)是指会话从创建到结束的时期,无论会 话正常关闭还是因超时而导致过期。一个会话的主要可能状态大多是简单明了的:CONNECTING、 CONNECTED、CLOSED和NOT_CONNECTED。

注意:发生网络分区时等待CONNECTING

如果一个客户端与服务器因超时而断开连接,客户端仍然保持 CONNECTING状态。如果因网络分区问题导致客户端与ZooKeeper集 合被隔离而发生连接断开,那么其状态将会一直保持,直到显式地关闭 这个会话,或者分区问题修复后,客户端能够获悉ZooKeeper服务器发 送的会话已经过期。发生这种行为是因为ZooKeeper集合对声明会话超时负责,而不是客户端负责。直到客户端获悉ZooKeeper会话过期,否 则客户端不能声明自己的会话过期。然而,客户端可以选择关闭会话。

创建一个会话时,你需要设置会话超时这个重要的参数,这个参数 设置了ZooKeeper服务允许会话被声明为超时之前存在的时间。如果经 过时间t之后服务接收不到这个会话的任何消息,服务就会声明会话过 期。而在客户端侧,如果经过t/3的时间未收到任何消息,客户端将向服 务器发送心跳消息。在经过2t/3时间后,ZooKeeper客户端开始寻找其他 的服务器,而此时它还有t/3时间去寻找。

当尝试连接到一个不同的服务器时,非常重要的是,这个服务器的 ZooKeeper状态要与最后连接的服务器的ZooKeeper状态保持最新。客户 端不能连接到这样的服务器:它未发现更新而客户端却已经发现的更 新。

2.3.3 ZooKeeper与仲裁模式

在一台服务器上部署多个zookeeper,端口号要不同。server.n指定了编号为n的zookeeper服务器使用的地址和端口号。每个server.n项通过冒号分隔为三部分,第一部分为服务器n的IP 地址或主机名(hostname),第二部分和第三部分为TCP端口号,分别 用于仲裁通信和群首选举。在不同的 服务器上运行每个服务器进程,因此每个服务器项的配置可以使用相同 的端口号。

还需要创建data目录,设置服务器ID信息,命令为:echo n > ./zk/data/myid,当服务器启动时,服务器通过配置文件中的dataDir参数来查找data 目录的配置。它通过mydata获得服务器ID,之后使用配置文件中server.n 对应的项来设置端口并监听。当在不同的机器上运行ZooKeeper服务器 进程时,它们可以使用相同的客户端端口和相同的配置文件。

  • dataDir=./zk/data
  • clientPort=2181
  • server.1=127.0.0.1:2222:2223
  • server.2=127.0.0.1:3333:3334
  • server.3=127.0.0.1:4444:4445

可以使用bin/zkCli.sh -server 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183 访问集群,客户端以随机顺序连接到连接串中的服务器。这样可以用 ZooKeeper来实现一个简单的负载均衡。不过,客户端无法指定优先选 择的服务器来进行连接,但可以配置客户端的连接串来控制随机连接的服务器范围。

2.3.4 实现一个原语:通过ZooKeeper实现锁

可以通过创建名为/lock的临时节点来实现,其他客户端创建该节点时会返回Node already exists:/znode。

2.4 一个主-从模式例子的实现

主从模型包含三个角色:主节点、从节点、客户端。

2.4.1 主节点角色

创建一个临时节点: create -e /master "主节点连接",其他节点创建时,会返回Node already exists: /master,这样其他进程就会知道/master已经存在。一个活动的主节点可能会崩溃,备份主节点需要接替活动主节点的角色。为了检测到这些,需要/master节点上设置一个监视点:stat /master true。主节点崩溃时,监视点会收到NodeDeleted事件。这个事件指出活动主节点的会话已经关闭或过期。备份节点可以成功创建/master成为主节点。

2.4.2 从节点、任务和分配

先创建三个重要的持久性父znode,/workers、/tasks和/assign。主节点需要监视/workers和/tasks的子节点的变化情况:ls /workers true、ls /tasks true。

2.4.3 从节点角色

从节点通过在/workers子节点下创建临时性的znode来进行通知,并在子节点中使用主机名来标识自己。同时在assign下创建持久性znode,同时使用ls监听子节点的变化,用来接收任务。

2.4.4 客户端角色

客户端在/tasks下创建有序节点(create -s /tasks/task- "task"),同时监听创建成功后的节点下的子节点(ls /task/task-order true),主节点之后会检查这个新的任务,获取可用的从节点列表,之后分配这个任务可用的从节点,即在/assign/从节点/task-order,从节点监控到分配给自己的任务,进行执行,同时更新执行结果到/task/task-order/status "result",客户单监控到执行结果。

不要自己试着去管理ZooKeeper客户端连接。ZooKeeper客户端库 会监控与服务之间的连接,客户端库不仅告诉我们连接发生问题,还会 主动尝试重新建立通信。一般客户端开发库会很快重建会话,以便最小 化应用的影响。所以不要关闭会话后再启动一个新的会话,这样会增加 系统负载,并导致更长时间的中断。

客户端断开后,服务器不会立即结束会话,直到会话超时时间 过了以后,服务器才会结束这个会话。要想使会话立即消失。这可以通过 ZooKeeper.close()方法来结束。一旦调用close方法后,ZooKeeper对 象实例所表示的会话就会被销毁。

2.4.5 异常处理

zookeeper的create同步方法会抛出两种异常:KeeperException和InterruptedException创建主节点时,需要确保我们处理了这两 种异常,特别是ConnectionLossException(KeeperException异常的子 类)和InterruptedException。对于这两种异常,create方法可能已经成功了,所以如果我们作为主节点就需要捕获并处理它们。

ConnectionLossException异常发生于客户端与ZooKeeper服务端失去 连接时。一般常常由于网络原因导致,如网络分区或ZooKeeper服务器 故障。当这个异常发生时,客户端并不知道是在ZooKeeper服务器处理 前丢失了请求消息,还是在处理后客户端未收到响应消息。ZooKeeper的客户端库将会为后续请求重新建立连接,但进 程必须知道一个未决请求是否已经处理了还是需要再次发送请求。

InterruptedException异常源于客户端线程调用了Thread.interrupt,通常这是因为应用程序部分关闭,但还在被其他相关应用的方法使用。

这两种请求都会导致正常请求处理过程的中断,开发者不能假设处 理过程中的请求的状态。当我们处理这些异常时,开发者在处理前必须 知道系统的状态。如果发生群首选举,在我们没有确认情况之前,我们 不希望确定主节点。如果create执行成功了,活动主节点死掉以前,没 有任何进程能够成为主节点,如果活动主节点还不知道自己已经获得了 管理权,不会有任何进程成为主节点进程。

处理ConnectionLossException异常时,我们需要找出那个进程创 建的/master节点,如果进程是自己,就开始成为群首角色。通过getData方法进行处理 byte[] getData(String, path, boolean watch, Stat stat).

InterruptedException异常的处理依赖于程序的上下文环境,如果向 上抛出InterruptedException异常,最终关闭zk句柄,我们可以抛出异常 到调用栈顶,当句柄关闭时就可以清理所有一切。如果zk句柄未关闭, 在重新抛出异常前,我们需要弄清楚自己是不是主节点,或者继续异步 执行后续操作。后者情况非常棘手,需要我们仔细设计并妥善处理。

2.4.6 异步创建节点

create方法的异步方法与同步方法非常相似,仅仅多了两个参数,该方法调用后通常在create请求发送到服务端之前就会立即返回。 回调对象通过传入的上下文参数来获取数据,当从服务器接收到create 请求的结果时,上下文参数就会通过回调对象提供给应用程序。该create方法不会抛出异常,调用 返回前并不会等待create命令完成,所以我们无需关心 InterruptedException异常;同时因请求的所有错误信息通过回调对象会 第一个返回,所以我们也无需关心KeeperException异常。

void create(String path,
    byte[] data,
    List<ACL> acl,
    CreateMode createMode,
    AsyncCallback.StringCallback cb,//提供回调方法的对象。
    Object ctx //用户指定上下文信息(回调方法调用是传入的对象实例)
)

回调对象实现只有一个方法的StringCallback接口:void processResult(int rc, String path, Object ctx, String name)。当接收到响应信息,这些请求就会在一个专用回调 线程中被处理。为了保持顺序,只会有一个单独的线程按照接收顺序处 理响应包。

  • rc :返回调用的结构,返回OK或与KeeperException异常对应的编码值。如果不为0,则对应KeeperException异常。
  • path:create的path参数值。
  • ctx:我们传给create的上下文参数。
  • name :创建的znode节点名称。

注意:回调函数处理, 因为只有一个单独的线程处理所有回调调用,如果回调函数阻塞, 所有后续回调调用都会被阻塞,也就是说,一般不要在回调函数中集中 操作或阻塞操作。有时,在回调函数中调用同步方法是合法的,但一般 还是避免这样做,以便后续回调调用可以快速被处理。

应用程序常常由异步变化通知所驱动,因此最终以异步方 式构建系统,反而使代码更简单。同时,异步调用不会阻塞应用程序, 这样其他事务可以继续进行,甚至是提交新的ZooKeeper操作。

ZooKeeper会严格地维护执行顺序,并提供了强有力的有序保障, 然而,在多线程下还是需要小心面对顺序问题。多线程下,当回调函数 中包括重试逻辑的代码时,一些常见的场景都可能导致错误发生。当遇 到ConnectionLossException异常而补发一个请求时,新建立的请求可能 排序在其他线程中的请求之后,而实际上其他线程中的请求应该在原来 请求之后。如果需要,可以执行无条件更新(第三个参数值为-1,表示禁止版本号检 查)。

三、处理状态变化

为了避免 轮询的调优和轮询流量。ZooKeeper提供了处理变化的重要机制——监 视点(watch)。通过监视点,客户端可以对指定的znode节点注册一个 通知请求,在发生变化时就会收到一个单次的通知。监视点和通知形成了一个通用机制,使客户端可以观察变化情况, 而不用不断地轮询ZooKeeper。

3.1 单次触发器

程序注册了一个监视点来接收通知,匹配该监视点条件的第 一个事件会触发监视点的通知,并且最多只触发一次。

客户端设置的每个监视点与会话关联,如果会话过期,等待中的监 视点将会被删除。不过监视点可以跨越不同服务端的连接而保持,例 如,当一个ZooKeeper客户端与一个ZooKeeper服务端的连接断开后连接 到集合中的另一个服务端,客户端会发送未触发的监视点列表,在注册 监视点时,服务端将要检查已监视的znode节点在之前注册监视点之后 是否已经变化,如果znode节点已经发生变化,一个监视点的事件就会 被发送给客户端,否则在新的服务端上注册监视点。

单次触发会丢失事件,但任务不会丢失,一个应用在接收到通知后,注册另一个监视点时, 可能会丢失事件,丢失事件通常并不 是问题,因为任何在接收通知与注册新监视点之间的变化情况,均可以 通过读取ZooKeeper的状态信息来获得。假设一个从节点接收到一个新任务分配给它的通知。为了接收新任 务,从节点读取任务列表,如果在通知接收后,又给这个从节点分配了 更多的任务,在通过getChildren调用获取任务列表时会返回所有的任 务。同时调用getChildren时也可以设置新的监视点,从而保证从节点不 会丢失任务。应 用进行高频率的更新操作时,这种通知机制比每个事件都发送通知更加 轻量化。

3.2 如何设置监视点

ZooKeeper的API中的所有读操作:getData、getChildren和exists, 均可以选择在读取的znode节点上设置监视点。使用监视点机制,我们 需要实现Watcher接口类,实现其中的process方法:

public void process(WatchedEvent event);

WatchedEvent数据结构包括以下信息:
·ZooKeeper会话状态(KeeperState):Disconnected、SyncConnected、AuthFailed、ConnectedReadOnly、                                                                                   SaslAuthenticated和Expired。
·事件类型(EventType):NodeCreated、NodeDeleted、NodeDataChanged、NodeChildrenChanged和None。
·如果事件类型不是None时,返回一个znode路径。

监视点有两种类型:数据监视点和子节点监视点。创建、删除或设 置一个znode节点的数据都会触发数据监视点,exists和getData这两个操 作可以设置数据监视点。只有getChildren操作可以设置子节点监视点, 这种监视点只有在znode子节点创建或删除时才被触发。

ZooKeeper节点的事件的通 知,你可以使用默认的监视点,也可以单独实现一个。

public byte[] getData(final String path, Watcher watcher, Stat stat);
public byte[] getData(String path, boolean watch, Stat stat);

监视点的一个重要问题是,一旦设置监视点就无法移除。要想 移除一个监视点,只有两个方法,一是触发这个监视点,二是使其会话 被关闭或过期。

3.3 另一种调用方式:Multiop

Multiop并非ZooKeeper的原始设计,该特性在3.4.0版本中被添加进 来。Multiop可以原子性地执行多个ZooKeeper的操作,执行过程为原子 性,即在multiop代码块中的所有操作要不全部成功,要不全部失败。

创建一个Op对象,该对象表示你想通过multiop方法执行的每个 ZooKeeper操作,ZooKeeper提供了每个改变状态操作的Op对象的实 现:create、delete和setData。

multi方法同样也有异步版本,以下为同步方法和异步方法的定义:

public List<OpResult> multi(Iterable<Op> ops) throws InterruptedException,
KeeperException;
public void multi(Iterable<Op> ops, MultiCallback cb, Object ctx);

Transaction封装了multi方法,提供了简单的接口。我们可以创建 Transaction对象的实例,添加操作,提交事务。

Transaction t = new Transaction();
t.delete("/a/b", -1);
t.delete("/a", -1);
List<OpResult> results = t.commit();

commit方法同样也有一个异步版本的方法,该方法以MultiCallback 对象和上下文对象为输入:public void commit(MultiCallback cb, Object ctx);

multiop提供的另一个功能是检查一个znode节点的版本。public static Op check(String path, int version);

3.4 监视点的羊群效应和可扩展性

有一个问题需要注意,当变化发生时,ZooKeeper会触发一个特定 的znode节点的变化导致的所有监视点的集合。如果有1000个客户端通 过exists操作监视这个znode节点,那么当znode节点创建后就会发送1000 个通知,会产生一个尖峰的通知,在尖峰时刻提交的操作延迟。最好是每次在特定的znode节点上,只有少量的客户端设置监视 点,理想情况下最多只设置一个。

另一方面需要注意的问题,就是当服务端一侧通过监视点产生的状 态变化。设置一个监视点需要在服务端创建一个Watcher对象,根据 YourKit(http://www.yourkit.com/ )的分析工具所分析,设置一个监视 点会使服务端的监视点管理器的内存消耗上增加大约250到300个字节, 设置非常多的监视点意味着监视点管理器会消耗大量的服务器内存。因此,开发 者必须时刻注意设置的监视点数量。

四、故障处理

故障发生的主要点有三个:ZooKeeper服务、网络、应用程序。

为了使连接断开与重现建立会话之间更加平滑,ZooKeeper客户端 库会在新的服务器上重新建立所有已经存在的监视点。当客户端连接 ZooKeeper的服务器,客户端会发送监视点列表和最后已知的zxid(最 终状态的时间戳),服务器会接受这些监视点并检查znode节点的修改 时间戳与这些监视点是否对应,如果任何已经监视的znode节点的修改 时间戳晚于最后已知的zxid,服务器就会触发这个监视点。

每个ZooKeeper操作都完全符合该逻辑,除了exists。exists操作与其 他操作不同,因为这个操作可以在一个不存在的节点上设置监视点。客户端监视/event节点的创建事件,然而就 在/event被另一个客户端创建时,设置了监视点的客户端与ZooKeeper间 失去连接,在这段时间,其他客户端删除了/event,因此当设置了监视 点的客户端重新与ZooKeeper建立连接并注册监视点,ZooKeeper服务器 已经不存在/event节点了,因此,当处理已经注册的监视点并判断/event 的监视时,发现没有/event这个节点,所以就只是注册了这个监视点, 最终导致客户端错过了/event的创建事件。因为这种特殊情况,你需要 尽量避免监视一个znode节点的创建事件,如果一定要监视创建事件, 应尽量监视存活期更长的znode节点。

有些ZooKeeper的封装库通过简单的补发命令自动处理连接丢失的 故障,有些情况这样做完全可以接受,但有些情况可能会导致错误的结 果。例如,如果/leader节点用来建立领导权,你的程序在执行create操作 建立/leader节点时连接丢失,而盲目地重试create操作会导致第二个 create操作执行失败,因为/leader节点已经存在,因此该进程就会假设其 他进程获得了领导权,使用到了这种库,最好能理解ZooKeeper的原理以及该库提 供给你的保障机制。

不可恢复的故障,会话过期和已认证的会话无法再次与ZooKeeper完成认证,都会导致zookeeper丢弃会话状态。处理不可恢复故障的最简单方法就是中止进程并重启。有些情况自动恢复机制工作得很好,比如客户端只读取数 据某些情况,但是如果客户端修改ZooKeeper中的数据,从会话故障中 自动恢复的危害就非常重要。

4.1 群首选举和外部资源

ZooKeeper为所有客户端提供了系统的一致性视图,只要客户端与 ZooKeeper进行任何交互操作(我们例子中所进行的操作),ZooKeeper 都会保持同步。然而,ZooKeeper无法保护与外部设备的交互操作。

当运行客户端进程的主机发生过载,就会开始发生交换、系统颠簸 或因已经超负荷的主机资源的竞争而导致的进程延迟,这些都会影响与 ZooKeeper交互的及时性。一方面,ZooKeeper无法及时地与ZooKeeper 服务器发送心跳信息,导致ZooKeeper的会话超时,另一方面,主机上 本地线程的调度会导致不可预知的调度:一个应用线程认为会话仍然处 于活动状态,并持有主节点。会话超时的示例:java应用程序进行垃圾回收时,可能导致进程暂停很长时间;时钟偏移,系统超载导致时钟冻结,客户端认为字节还安全的处于超时周期内,而没有及时的发送心跳。

资源中心化管理,让应用程 序通过使用ZooKeeper来确保每次只有一个主节点可以独占访问一个外 部资源,来确保一致性。

解决这个问题有几个方法:一个方法是确保你的应用不会在超载或 时钟偏移的环境中运行,小心监控系统负载可以检测到环境出现问题的 可能性,良好设计的多线程应用也可以避免超载,时钟同步程序可以保 证系统时钟的同步。另一个方法是通过ZooKeeper扩展对外部设备协作的数据,使用一 种名为隔离(fencing)的技巧,分布式系统中常常使用这种方法用于确 保资源的独占访问。

五、ZooKeeper注意事项

5.1 使用ACL

对于ZooKeeper,开发人员往往负责管理访问控制的权限,而不是 管理员。这是因为每次创建znode节点时,必须设置访问权限,而且子 节点并不会继承父节点的访问权限。

一个进程可以在任何时候调用addAuthInfo来添加鉴权信息。一般情 况下,在ZooKeeper句柄创建后就会调用该方法来添加鉴权信息。进程 中可以多次调用该方法,为一个ZooKeeper句柄添加多个权限的身份。

5.1.1 内置的鉴权模式

ZooKeeper提供了4种内置模式进行ACL的处理。

OPEN_ACL_UNSAFE常量隐式传递了ACL策略:这 种ACL使用world作为鉴权模式,使用anyone作为auth-info,对于world 这种鉴权模式,只能使用anyone这种auth-info。

管理员所使用的super模式,该模式不会被 列入到任何ACL中,但可以用于ZooKeeper的鉴权。一个客户端通过 super鉴权模式连接到ZooKeeper后,不会被任何节点的ACL所限制。

digest为内置鉴权模式,该模式的auth-info格式为userid: passwd_digest,当调用addAuthInfo时需要设置ACL和userid:password信 息。其中passwd_digest为用户密码的加密摘要。digest:dom:XXXXX, READ | WRITE | CREATE | DELETE | ADMIN

ip鉴权模式需要提供网络的地址和掩码,ip:10.11.12.0/24, READ,,客户端在使用ip模式的ACL策略访问znode节 点时,不需要调用addAuthInfo方法。

5.1.2 SASL和Kerberos

SASL表示简单认证与安全层(Simple Authentication and Security Layer)。SASL将底层系统的鉴权模型抽象为一个框架,因此应用程序 可以使用SASL框架,并使用SASL支持多各种协议。在ZooKeeper中, SASL常常使用Kerberos协议,该鉴权协议提供之前我们提到的那些缺失 的功能。在使用SASL模式时,使用sasl作为模式名,id则使用客户端的 Kerberos的ID。

SASL是ZooKeeper的扩展鉴权模式,因此,需要通过配置参数或 Java系统中参数激活该模式。如果你采用ZooKeeper的配置文件方式, 需要使用authProvider.XXX配置参数,如果你想要通过系统参数方式, 需要使用zookeeper.authProvider.XXX作为参数名。这两种情况下,XXX 可以为任意值,只要没有任何重名的authProvider,一般XXX采用以0开 始的一个数字。配置项的参数值为 org.apache.zookeeper.server.auth.SASLAuthenticationProvider,这样就可 以激活SASL模式。

5.2 sync方法

sync方法的设计初衷,是因为与ZooKeeper的带外通信可能 会导致某些问题,这种通信常常称为隐蔽通道(hidden channel),sync方法的path参数指示需要进行操作的路径。在系统内部,sync 方法实际上并不会影响ZooKeeper,当服务端处理sync调用时,服务端 会刷新群首与调用sync操作的客户端c所连接的服务端之间的通道,刷 新的意思就是说在调用getData的返回数据的时候,服务端确保返回所有 客户端c调用sync方法时所有可能的变化情况。

ZooKeeper的设计初衷是用于快 速读取以及以读为主要负载的扩展性考虑,所以简化了sync的实现,同 时与其他常规的更新操作(如create、setData或delete)不同,sync操作 并不会进入执行管道之中。sync操作只是简单地传递到群首,之后群首 会将响应包队列化,传递给群组成员,之后发送响应包。不过还有另外 一种可能,仲裁机制确定的群首l',现在已经不被仲裁组成员所认可, 仲裁组成员现在选举了另个群首l',在这种情况下,群首l可能无法处理 所有的更新操作的同步,而sync调用也就可能无法履行其保障。

5.3 顺序性保障

5.3.1 连接丢失时的顺序性

对于连接丢失事件,ZooKeeper会取消等待中的请求,对于同步方 法的调用客户端库会抛出异常,对于异步请求调用,客户端调用的回调 函数会返回结果码来标识连接丢失。确保Op1先于Op2成功执行可能是重要问题。如果 Op2在某种程度上依赖与Op1操作,为了避免Op2在Op1之前成功执行, 我们可以等待Op1成功执行之后在提交Op2请求,等 待Op1操作结果的方法很安全,但是带来了性能上的损失。

5.3.2 数据字段和子节点的限制

ZooKeeper默认情况下对数据字段的传输限制为1MB,该限制为任 何节点数据字段的最大可存储字节数,同时也限制了任何父节点可以拥 有的子节点数。

六、Curator:ZooKeeper API的高级封装库

6.1 Curator客户端程序

CuratorFramework zkc = CuratorFrameworkFactory.newClient(connectString, retryPolicy);

  • connectString输入参数为我们将要连接的ZooKeeper服务器的 列表.
  • retryPolicy参数为Curator提供 的新特性,通过这个参数,开发人员可以指定对于失去连接事件重试操 作的处理策略。

其他工程类如CuratorZooKeeperClient,该类中的操作执行与ZooKeeper客户端句柄直接相 对应。

6.2 流畅式API

流畅式API可以让我们编写链式调用的代码,而不用在进行请求操 作时采用严格的签名方案。

zkc.create().withMode(CreateMode.PERSISTENT).forPath("/mypath", new byte[0]);

异步:zkc.create().inBackground().withMode(CreateMode.PERSISTENT).forPath("/mypath", new byte[0]);

监视点:

  • zkc.getData().inBackground().watched().forPath("/mypath");
  • 使用usingWathcer方 法
  • 传入一个CuratorWatcher对象

6.3 监听器

监听器(listener)负责处理Curator库所产生的事件。

CuratorListener masterListener = new CuratorListener() {
public void eventReceived(CuratorFramework client, CuratorEvent event) {
try {
switch (event.getType()) {
case CHILDREN:
...
break;
case CREATE:
...
break;
case DELETE:
...
break;
case WATCHED:
...
break;
}
} catch (Exception e) {
LOG.error("Exception while processing event.", e);
try {
close();
} catch (IOException ioe) {
LOG.error("IOException while closing.", ioe);
}
}
};

注册监听器:

client = CuratorFrameworkFactory.newClient(hostPort, retryPolicy);

client.getCuratorListenable().addListener(masterListener);

还有一类比较特殊的监听器,这类监听器负责处理后台工作线程捕 获的异常时的错误报告,该类监听器提供了底层细节的处理。

UnhandledErrorListener errorsListener = new UnhandledErrorListener() {
public void unhandledError(String message, Throwable e) {
LOG.error("Unrecoverable error: " + message, e);
try {
close();
} catch (IOException ioe) {
LOG.warn( "Exception when closing.", ioe );
}
}
};

client.getUnhandledErrorListenable().addListener(errorsListener);

6.4 Curator中状态的转换

处理状态的转换时,我们建议将 所有主节点操作请求暂停,因为我们并不知道ZooKeeper客户端能否在 会话过期前重新连接,即使ZooKeeper客户端重新连接成功,也可能不 再是主要主节点的角色,因此谨慎处理连接丢失的情况,对应用程序更 加安全。

状态还有一个READ_ONLY状态,当 ZooKeeper集群启用了只读模式,客户端所连接的服务器就会进入只读 模式中,此时的连接状态也将进入只读模式。服务器转换到只读模式 后,该服务器就会因隔离问题而无法与其他服务器共同形成仲裁的最低 法定数量,当连接状态为制度模式,客户端也将漏掉此时发生的任何更 新操作,因为如果集群中存在一个子集的服务器数量,可以满足仲裁最 低法定数量,并可以接收到客户端的对ZooKeeper的更新操作,还是会 发生ZooKeeper的更新,也许这个子集的服务器会持续运行很久 (ZooKeeper无法控制这种情况),那么漏掉的更新操作可能会无限 多。漏掉更新操作的结果可能会导致应用程序的不正确的操作行为,所 以,我们强烈建议启用该模式前仔细考虑其后果。注意,只读模式并不 是Curator所独有的功能,而是通过ZooKeeper启用该选项。

6.5 两种边界情况

两种有趣的错误场景,在Curator中都可以处理得很好,第一种是 在有序节点的创建过程中发生的错误情况的处理,第二种为删除一个节 点时的错误处理。

如果客户端所连接的服务器崩溃了,但还没来得及返回客户端所创 建的有序节点的节点名称(即节点序列号),或者客户端只是连接丢 失,客户端没接收到所请求操作的响应信息,结果,客户端并不知道所 创建的znode节点路径名称。CreateBuilder提供了一个withProtection方法来通知Curator客户端,在创 建的有序节点前添加一个唯一标识符,如果create操作失败了,客户端 就会开始重试操作,而重试操作的一个步骤就是验证是否存在一个节点 包含这个唯一标识符。

进行delete操作时也可能发生类似情况,如果客户端在执行delete 操作时,与服务器之间的连接丢失,客户端并不知道delete操作是否成 功执行。Curator客户端中提供了一个方法,对应用程序 的delete操作的执行提供了保障,只需要使 用DeleteBuilder接口中定义的guaranteed方法。

6.6 菜谱

我们的Curator主节点例子的实现中,用到了三种菜谱: LeaderLatch、LeaderSelector和PathChildrenCache。

群首闩(leader latch):leaderLatch = new LeaderLatch(client, "/master", myId);

群首选举器(LeaderSelector):LeaderSelector和LeaderLatch之间主要区别在于使用的监听器接口不同, 其中LeaderSelector使用了LeaderSelectorListener接口,该接口中定义了 takeLeadership方法,并继承了stateChanged方法。leaderSelector = new LeaderSelector(client, "/master", this);

子节点缓存器:PathChildrenCache

七、ZooKeeper内部原理

7.1 请求、事务和标识符

ZooKeeper服务器会在本地处理只读请求(exists、getData和 getChildren)。处理以只读请求为主要负载时,性能会很高。

改变ZooKeeper状态的客户端请求(create、delete和 setData)将会被转发给群首,群首执行相应的请求,并形成状态的更 新,我们称为事务(transaction)。一个事务为一个单位,也就是说所有的变更处理需要以原子方式执 行。zookeeper多线程的支持,提高事务处理的速度。事务还具有幂等性,也就是说,我们可以对同一个事务执 行两次,我们得到的结果还是一样。

当群首产生了一个事务,就会为该事务分配一个标识符,我们称之 为ZooKeeper会话ID(zxid),通过Zxid对事务进行标识,就可以按照群 首所指定的顺序在各个服务器中按序执行。服务器之间在进行新的群首 选举时也会交换zxid信息,这样就可以知道哪个无故障服务器接收了更 多的事务,并可以同步他们之间的状态信息。zxid为一个long型(64位)整数,分为两部分:时间戳(epoch)部 分和计数器(counter)部分。每个部分为32位。

7.2 群首选举

只有最新的服务器将赢得选举,因为其拥有最近一次的 zxid。

6.3 Zab:状态更新的广播协议

接收到一个写请求操作后,追随者会将请求转发给群首,群首将 探索性地执行该请求,并将执行结果以事务的方式对状态更新进行广 播。一个事务中包含服务器需要执行变更的确切操作,当事务提交时, 服务器就会将这些变更反馈到数据树上,其中数据树为ZooKeeper用于 保存状态信息的数据结构(请参考DataTree类)。

6.4 观察者

观察者。观察者和追随者之间有一些共同点。具体说来,他们提 交来自群首的提议。不同于追随者的是,观察者不参与我们之前介绍过 的选举过程。观察者(observer)为ZooKeeper服务器中不参 与投票但保障状态更新顺序的特殊服务器。

引入观察者的一个主要原因是提高读请求的可扩展性。通过加入多 个观察者,我们可以在不牺牲写操作的吞吐率的前提下服务更多的读操 作。写操作的吞吐率取决于仲裁数量的大小。

采用观察者的另外一个原因是进行跨多个数据中心的部署。

七、运行zookeeper

7.1 配置ZooKeeper服务器

7.1.1 基本配置

  • clientPort 客户端所连接的服务器所监听的TCP端口,默认端口号为2181。
  • dataDir和dataLogDir  dataDir用于配置内存数据库保存的模糊快照的目录,如果某个服务 器为集群中的一台,id文件也保存在该目录下。dataDir并不需要配置到一个专用存储设备上,快照将会以后台线程 的方式写入,且并不会锁定数据库。事务日志对该目录所处的存储设备上的其他活动更加敏感,服务端 会尝试进行顺序写入事务日志,以为服务端在确认一个事务前必须将数 据同步到存储中,该设备的其他活动(尤其是快照的写入)可能导致同 步时磁盘过于忙碌,从而影响写入的吞吐能力。因此,最佳实践是使用 专用的日志存储设备,将dataLogDir的目录配置指向该设备。
  • tickTime 单位为毫秒,会话超时时间,客户端最小会话超时事件为两个tick时间,tickTime的默认值为3000毫秒,更低的tickTime值可以更快地发现 超时问题,但也会导致更高的网络流量(心跳消息)和更高CPU使用率 (会话存储器的处理)。
  • preAllocSize 设置预分配的事务日志文件(zookeeper.preAllocSize)的大小 值,以KB为单位。当写入事务日志文件时,服务端每次会分配preAllocSize值的KB的 存储大小,通过这种方式可以分摊文件系统将磁盘分配存储空间和更新 元数据的开销,更重要的是,该方式也减少了文件寻址操作的次数。默认情况下preAllocSize的值为64MB。缩小该值的一个原因是事务 日志永远不会达到这么大,因为每次快照后都会重新启动一个新的事务 日志,如果每次快照之间的日志数量很小,而且每个事务本身也很小, 64MB的默认值显然就太大了。默认的preAllocSize值的设置适用于默认的snapCount值和平 均事务超过512字节的情况。
  • snapCount 每次快照之间的事务数(zookeeper.snapCount)默认值为100000,进行快照时会影响性能。
  • autopurge.snapRetainCount  指定了垃圾回收时需要保留的快照数,最小值为3,也是默认值的大 小。
  • autopurge.purgeInterval 对快照和日志进行垃圾回收(清理)操作的时间间隔的小时数。
  • fsync.warningthresholdms 触发警告的存储同步时间阀值(fsync.warningthresholdms),以毫 秒为单位。默认情况下,该值为1000毫秒。
  • weight.x=n 指定组成一个仲裁机构的 某个服务器的权重为n,其权重n值指示了该服务器在进行投票时的权重 值。默认为1。
  • maxClientCnxns 允许每个IP地址的并发socket连接的最大数量。默认值为 60个并发连接。每个服务器维护着这个连接的数量。
  • clientPortAddress  限制客户端连接到指定的接收信息的地址上。默认情况下,一个 ZooKeeper服务器会监听在所有的网络接口地址上等待客户端的连接。有些服务器配置了多个网络接口,其中一个网络接口用于内网通 信,另一个网络接口用于公网通信,如果你并不希望服务器在公网接口 接受客户端的连接,只需要设置clientPortAddress选项为内网接口的地 址。
  • minSessionTimeout 最小会话超时时间,单位为毫秒。客户端建立一个连接后就会请 求一个明确的超时值,客户端实际获得的超时值不会低于 minSessionTimeout的值。minSessionTimeout的默认值为tickTime值的两倍。配置该参数值过 低可能会导致错误的客户端故障检测,配置该参数值过高会延迟客户端 故障的检测时间。
  • maxSessionTimeout  会话的最大超时时间值,单位为毫秒。默认情况下maxSessionTimeout的时间为tickTime的20 倍。
  • initLimit  追随者最初连接到群首时的超时值,单位为tick值的倍数。配置initLimit参数值取决于群首与 追随者之间的网络传输速度情况,以及传输的数据量大小。该值取决于环境 问题,所有没有默认值。如果配置initLimit值过高,那么首次连接到故障 的服务器就会消耗更多的时间,同时还会消耗更多的恢复时间,最 好在你的网络中进行追随者与群首之间的网络基准测试,以你规划所使 用的数据量来测试出你所期望的时间。
  • syncLimit 追随者与群首进行sync操作时的超时值,单位为tick值的倍 数。如果群首 与追随者无法进行sync操作,而且超过了syncLimit的tick时间,就会放 弃该追随者。与initLimit参数类似,syncLimit也没有默认值,与initLimit 不同的是,syncLimit并不依赖于ZooKeeper中保存的数据量大小,而是 依赖于网络的延迟和吞吐量。在高延迟网络环境中,发送数据和接收响 应包会耗费更多时间,此时就需要调高syncLimit值。即使在相对低延迟 的网络中,如果某些相对较大的事务传输给追随者需要一定的时间,你 也需要提高syncLimit值。
  • leaderServes 配置值为“yes”或“no”标志,指示群首服务器是否为客户端提供服务 (zookeeper.leaderServes)。担任群首的ZooKeeper服务器需要做很多工作,它需要与所有追随 者进行通信并会执行所有的变更操作,也就意味着群首的负载会比追随 者的负载高,如果群首过载,整个系统可能都会受到影响。设置为“no”就可以使群首除去服务客户端连接的负 担,使群首将所有资源用于处理追随者发送给它的变更操作请求,这样 可以提高系统状态变更操作的吞吐能力。默认情况下,leaderServes的值为“yes”。
  • server.x=[hostname]:n:n[:observer]  hostname为服务器在网络n中的名称,同时后面跟了两个TCP的 端口号,第一个端口用于事务的发送,第二个端口用于群首选举,最后一个字段标记了observer属性, 服务器就会进入观察者模式。所有的服务器使用相同的server.x配置信息,这一点非常重 要,否则的话,因服务器之间可能无法正确建立连接而导致整个集群无 法正常工作。
  • cnxTimeout 群首选举打开一个新的连接的超时值(zookeeper.cnxTimeout)。ZooKeeper服务器在进行群首选举时互相之间会建立连接,该选项 值确定了一个服务器在进行重试前会等待连接成功建立的时间为多久,默认的超时时间为5秒。
  • electionAlg 选举算法的配置选项。除了默认的配置外,其他算法都已经弃用了,所 以你并不需要配置这个选项。
  • forceSync  通过“yes”或“no”选项可以控制是否将数据信息同步到存储设备上 (zookeeper.forceSync)。forceSync配置yes时,事务只有在同步到存储设备后 才会被应答,同步系统调用的消耗很大,而且也是事务处理中最大的延 迟原因之一。如果forceSync配置为no,事务会在写入到操作系统后就立 刻被应答,在将事务写入磁盘之前,这些事务常常缓存于内存之中,配 置forceSync为no可以提高性能,但代价是服务器崩溃或停电故障时可恢 复性。

7.1.2 日志

ZooKeeper采用SLF4J库(JAVA简易日志门面)作为日志的抽象 层,默认使用Log4J进行实际的日志记录功能。

7.1.3 法定人数的可配置性

当配置多个服务器来组成ZooKeeper集群时,我们默认使用多数原 则作为仲裁法定人数。ZooKeeper会自动监测是否运行于复制模式,从 配置文件读取时确定是否拥有多个服务器的配置信息,并默认使用多数 原则的仲裁法定人数。

但法定人数并未限制必须满足多数原则,ZooKeeper也允许灵活的法 定人数配置,这种特殊方案就是对服务器进行分组配置时,我们会将服 务器分组成不相交的集合并分配服务器的权重,通过这种方案来组成法 定人数,我们需要使多数组中的服务器形成多数投票原则。如,我们 有三个组,每个组中有三个服务器,每个服务器的权重值为1,在这种 情况下,我们需要四个服务器来组成法定人数:某个组中的两个服务 器,另一组中的两台服务器。

group.x=n[:n] 启用法定人数的分层构建方式。x为组的标识符,等号后面的数字 对应服务器的标识符,赋值操作符右侧为冒号分隔的服务器标识符的列 表。注意,组与组之间不能存在交集,所有组的并集组成了整个 ZooKeeper集群,换句话说,集群中的每个服务器必须在某个组中被列 出一次。

在跨多个数据中心部署ZooKeeper服务时,这种配置方式有 很多优点。例如,一个组可能表示运行于不同数据中心的一组服务器, 即使这个数据中心崩溃,ZooKeeper服务也可以继续提供服务。

7.1.4 观察者

peerType=observer

server.1:localhost:2181:3181:observer

7.1.5 重配置

重配置不仅可以让我们改变集群成员配置,还可以修改网络参数配 置,因为ZooKeeper中配置信息的变化,需要将重配置参数与静态的配 置文件分离,单独保存为一个配置文件并自动更新该文件。 dynamicConfigFile参数和链接这两个配置文件。

将配置文件修改为动态配置方式:dynamicConfigFile=./dyn.cfg

在dyn.cfg 文件由服务器项的配置组成,同时还多了一些配置:server.id=host:n:n[:role];[client_address:]client_port

role选项必须为participant或observer,如果 忽略role选项,默认为participant,我们还指定了client_port(用于客户端 连接的服务器端口号),以及该服务器需要绑定的特定网络接口地址, 因为我们从静态配置文件中删除了clientPort参数,所以我们在这里添加 该配置。如:server.1=127.0.0.1:2222:2223:participant;2181

通过reconfig操作来重新配置一个集群,该操作可以增量或全 量(整体)地进行更新操作。增量的重配置操作将会形成两个列表:待删除的服务器列表,待添 加的服务器项的列表。待删除的服务器列表仅仅是一个逗号分隔服务器 ID列表,待添加的服务器项列表为逗号分隔的服务器项列表,每个服务 器项的形式为动态配置文件中所定义的形式。例如:reconfig -remove 2,3 -add \ server.4=127.0.0.1:5555:5556:participant;2184,\ server.5=127.0.0.1:6666:6667:participant;2185

该操作成功执行还 需要满足某些条件,首先,与其他ZooKeeper操作一样,原配置中法定 人数必须处于活动状态;其次,新的配置文件中构成的法定人数也必须 处于活动状态。

不允许以独立模式运行重配置操作,只有在仲裁模式时才可以使用重 配置功能。

还可以使用-file参数来指定一个新的成员配置文件来进行一次 全量更新。例如:reconfig-file newconf命令会产生如上面命令一样的增 量操作结果,newconf文件为: server.1=127.0.0.1:2222:2223:participant;2181 server.4=127.0.0.1:5555:5556:participant;2184 server.5=127.0.0.1:6666:6667:participant;2185

通过-members参数,后跟服务器项的列表信息,可以代替-file参数 进行全量更新配置操作。

所有形式的reconfig的为重新配置提供了条件,如果通过-v参 数提供了配置版本号,reconfig命令会在执行前确认配置文件当前的版 本号是否匹配,只有匹配才会成功执行。

7.2 文件系统布局和格式

数据存储有两类:事务日志文件和快照文 件。这些文件均以普通文件的形式保存到本地文件系统中,在进行关键 路径的事务处理时就会写入事务日志文件,所以我们强烈建议将这些文 件保存到一个专用存储设备上(事实上我们已经多次提到这个问题,因 为这对于吞吐能力和延迟的一致性问题非常重要),不使用专用存储设 备保存事物日志文件并不会导致任何正确性相关的问题,却会影响性 能。对于快照文件并不 要求存储于专用存储设备上,因为该文件由后台线程慢慢写入。

快照文件将会被写入到DataDir参数所指定的目录中,而事务日志 文件将会被写入到DataLogDir参数所指定的目录中。

只有 变更操作才会被记录到事务日志,所以在事务日志中不会看到任何读事 务操作。

快照文件并不会被预分配空间,所以文件大小也更加准确地反映了 其中包含的数据大小。其中后缀表示快照开始时当时的zxid值,我们之 前已经介绍过,快照文件实际上为一个模糊快照,直到事务日志重现之 后才会成为一个有效的快照文件。因此在恢复系统时,你必须从快照后 缀的zxid开始重现事务日志文件,甚至更早的zxid开始重现事务。

时间戳文件:ZooKeeper的持久状态由两个小文件构成,它们是两个时间戳文 件,其文件名为acceptedEpoch和currentEpoch,这两个文件则反映了某个服务器进程已接受的和正在处 理的信息。这两个文件并不包含任何应用数据信息,对于数据一 致性却至关重要,所以如果你在备份一个ZooKeeper服务器的原始数据 文件时,不要忘了这两个文件。

7.3 四字母命令

  • ruok 提供(有限的)服务器的状态信息。如果服务器正在运行,就会返 回imok响应信息。
  • stat 提供了服务器的状态信息和当前活动的连接情况,状态信息包括一 些基本的统计信息,还包括该服务器当前是否处于活动状态,即作为群 首或追随者,该服务器所知的最后的zxid信息。某些统计信息为累计 值,我们可以使用srst命令进行重置。
  • srvr 提供的信息与stat一样,只是忽略了连接情况的信息。
  • dump 提供会话信息,列出当前活动的会话信息以及这些会话的过期时 间。该命令只能在群首服务器上运行。
  • conf 列出该服务器启动运行所使用的基本配置参数。
  • envi 列出各种各样的Java环境参数。
  • mntr 提供了比stat命令更加详细的服务器统计数据。每行输出的格式为 key<tab>value。(群首服务器还将列出只用于群首的额外参数信息)。
  • wchs 列出该服务器所跟踪的监视点的简短摘要信息。
  • wchc 列出该服务器所跟踪的监视点的详细信息,根据会话进行分组。
  • wchp 列出该服务器所跟踪的监视点的详细信息,根据被设置监视点的 znode节点路径进行分组。
  • cons,crst  cons命令列出该服务器上每个连接的详细统计信息,crst重置这些 连接信息中的计数器为0

八、总结

8.1 用于什么?

zookeeper专注于任务协作,不适用于海量数据存储,只管理协同数据,不应存储应用数据。

8.2 特性

强一致性、有序性、持久性、通用同步原语、简单的并发处理机制。

8.3 如何通信

分布式系统进程通信方式:网络通信(是基础)和共享存储。zookeeper使用共享存储模型,共享存储本身又在进程和存储间进行网络通信。

8.4 真实环境中要特别注意的问题

消息延迟、处理器性能、时钟偏移,这三种情况导致无法判断是进程崩溃还是延时。zookeeper透明化这些问题,在应用服务层面更容易处理。

8.5 节点类型

持久性、临时性、持久有序、临时有序 临时节点在创建该节点的会话过期或关闭时删除。

8.6 客户端如何获取节点状态变化?

注册监视点后,变化会通知给客户端。监视点为一次性,用后必须再次注册才能接收新的变化通知。

会话过服务器监视点会删除,但客户端重新连接后,会发送未触发的监视点并注册。

监视点包含:节点子节点监视点、节点数据监视点。

一个监视点消耗内存250到300字节。

8.7 新的监视点是否会错过设立前的状态变化?

(除了exist)基本上不会,新监视点设置前,会先读取zookeeper的状态。exist可以在不存在的节点上设置监视点,设置前节点如果被删除,则无法获取到。

会丢失事件,即设立前状态变化的通知。

8.8 zookeeper服务器模式

独立模式、仲裁模式(集群)

8.9 仲裁的运行机制

法定人数选举主节点、法定人数完成数据保存即为客户端操作完成。

8.10 一个会话中的请求会FIFO的顺序执行,多个并发会话或会话失效后再建则无法保证顺序。

8.11 会话生命周期也状态

生命周期指从创建到结束。

状态:connnecting、connected、closed、not_connected

服务器对会话超时负责,因此客户端因超时断开连接、会话网络分区问题导致客户端与服务隔离,都会使客户端获取不到会话过期状态而一直保持connecting。

8.12 客户端是否可以重新连接到任意服务器

客户端尝试连接新的服务器时,会通过zxid的一致性保证连接到的服务器状态与客户端之前连接的服务器状态一致,否则连接不成功。

8.14 客户端要重点处理的异常

KeeperException(ConnectionLossException):网络原因、InterruptedException:应用程序线程调用Thread.interrupt 因为这些异常发生时不知道请求是否执行成功。

8.15 主节点恢复的可能问题

脑裂:主节点负载高或其他原因导致的延迟导致备份主节点认为主节点崩溃

8.16 multiop 原子性执行多个操作。

8.17 客户端主键过载、java垃圾回收、时钟偏移会导致与zookeeper交换不及时或客户端时间理解偏差而导致会话超时。

8.18 zookeeper内部原理

服务器本地处理只读请求,所以只读请求性能高。 写入请求都会转移给群首,群首执行,产生事务并分配zxid,根据zxid按序执行。zxid为64为,时间戳和计数器,各32位。

8.19 主要配置

dataDir 快照,不需要配置专用存储,快照后台线程写入。

dataLogDir 事务日志目录,使用专用日志存储设备,避免其他操作引起磁盘忙碌进而影响写入的吞吐能力。

initLimit 追随者最初连接到群首时的超时值,最好进行追随者与群首之间的网络基准测试觉得大小。

syncLimit 追随者与群首进行sync操作时的超时值,依赖于网络的延迟和吞吐量。

leaderServes 群首服务器是否为客户端提供服务,默认yes。

forceSync 是否将数据信息同步到存储设备上 。no可以提高性能,但代价是服务器崩溃或停电故障时可恢复性。

8.20 法定人数原则

默认多数原则,也可以自定义分组配置。

8.21 动态扩展服务器

使用动态配置文件:dynamicConfigFile=./dyn.cfg 然后reconfig执行。

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值