分布式系统中,协调和管理服务是一个复杂的过程,数据的一致性也是一个不可避免的问题,而 zookeeper 是一个解决分布式集群应用中数据一致性问题的有效工具,它提供基于类似于文件系统的目录节点树方式的数据存储,但是 zookeeper 并不是用来专门存储数据,它的主要作用是用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。

zookeeper 的数据模型

zookeeper 的设计,借鉴了 Linux 中的文件系统,我们知道,在 Linux 中,用 / 来区分目录,如/ 表示根目录,/home 表示在根目录下有个 home 目录, 同样,在 zookeeper 中,/ 表示根节点, /master 表示根节点下的 master 节点,zookeeper 中的这种节点,被称为 znode。我们可以像创建目录一样,在 zookeeper 中创建任意一个节点。如图一。 图一

与 Linux 目录不同的是, znode 节点还有自己独特的一些属性。

  • 访问一个 znode 只能使用绝对路径,而不能使用相对路径,例如 .././ 这种表示在 zookeeper 中是不被允许的。
  • znode 节点中,也可以存放数据,但并不适合存放大量数据,znode 节点存储数据大小不能超过 1M。
  • 节点有持久节点 (persistent) 和临时节点 (ephemeral),在创建 znode 时指定,且后续不可修改。持久节点创建后,除非主动删除,否则会一直存在。临时节点创建后,一旦客户端与 zookeeper 会话断开,临时节点就会被删除。当然临时节点也可以被显示删除。节点的这种特定,很适合用来做分布式系统中节点的健康检查。
  • 节点可以有子节点,就像 Linux 某目录可以有子目录一样,但临时节点不可以有子节点
  • 节点可以被监控(watch),当某个节点被客户端监控,那么当该节点中存储的数据,或者该节点的子节点发送变化,zookeeper 会通知设置监控的客户端,那么客户端可以根据通知,做相应的变化。这是 zookeeper 的核心特性,许多应用场景,均是使用了该特性。

有了znode 的概念,应运而生,就有了操作 znode 的 API,zookeeper 提供了 Restful 的 API,可以很方便的对节点进行 CRUD 操作。

  • create 创建一个 znode
  • delete 删除一个 znode
  • getData 获取一个 znode 中的数据
  • exists 判断一个 znode 是否存在
  • getChildren 获取一个 znode 的所有子节点

zookeeper 部署模式

zookeeper 既可以以单节点模式运行,即只在一台机器上部署 zookeeper 服务,也可以以集群方式运行。事实上,为了发挥 zookeeper 的高可用特性,最佳实践应该以集群方式运行。幸运的是,在个人测试或者资源紧张时,在单台机器上,zookeeper 仍然可以模拟集群模式,即在一台机器上起多个 zookeeper 实例,只需要在配置文件中,修改指定配置项即可。

当 zookeeper 以集群方式运行时,会有一些独特的特性。

首先,并不是所有的节点都是一样的,zookeeper 集群中,会自主产生一个 leader 节点,其他都是 follower 节点,或者 observer 节点。所有对 zookeeper 的写操作,都会由 leader 节点来完成。读操作,可以由 follower 或者 leader 节点完成。事实上,当客户端连接到 zookeeper 时,它根本就不知道连接的是 leader 还是 follower。因此,当客户端有写操作,而它有恰恰连接到的是 follower 节点,那么该写操作仍然是转发给 leader 节点来完成。当 leader 节点挂掉时,zookeeper 会自动重新从剩余的节点中,再选一个 leader 出来。至于 leader 是怎么选出来的,这个涉及到 zookeeper 的选主算法,本文不做深入讨论。

其次,zookeeper 通过复制保证高可用性,每个 follower 节点的数据,都是 leader 节点数据的副本。集群中,只要有半数以上的节点处于可用状态,那么 zookeeper 就可以正常对外提供服务。如 5 个节点的 zookeeper 集群,就算有 2 个节点挂了,也可以保证服务继续,因为剩余的 3 个节点超过了半数。

再者,由于集群模式下的 zookeeper 服务有多个节点,同一时间,不同的客户端连接到 zookeeper,看到的数据应该是一样的,即 zookeeper 必须要保证数据一致性。zookeeper 从以下几点,保证了数据一致性

  • 顺序一致性 来自任意特定客户端的更新都会按其发送顺序被提交。也就是说,如果一个客户端将 znode z 的值更新为 a,在之后的操作中,它又将 z 的值更新为 b,则没有客户端能够在看到 z 的值是 b 之后再看到值 a(如果没有其他对z的更新)
  • 原子性 每个更新要么成功,要么失败。这意味着如果一个更新失败,则不会有客户端会看到这个更新的结果
  • 单一系统镜像 一个客户端无论连接到哪个节点,它看到的都是同样的系统视图。这意味着,如果一个客户端在同一个会话中连接到一台新的节点,它所看到的系统状态不会比在之前节点上所看到的更老。当一个节点出现故障,导致它的一个客户端需要尝试连接集合体中其他节点时,所有滞后于故障节点的节点都不会接受该连接请求,直到节点的状态赶上之前故障的节点为止。
  • 持久性 一个更新一旦成功,其结果就会持久存在并且不会被撤销。这表明更新不会受到服务器故障的影响。

zookeeper 的使用场景

了解了 zookeeper 的一些基本概念,我们对 zookeeper 有了宏观上的认识,zookeeper 的这种设计,类似于设计模式中的观察者模式,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应,从而实现集群中类似 Master/Slave 管理模式。

下面看看使用 zookeeper 的几个场景。

统一命名 (Name Service)

分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,就像数据库中产生一个唯一的数字主键一样,通常情况下用树形的名称结构是一个理想的选择。这里的名称是很宽泛的,可以是指集群的机器名,也可以是提供的服务地址,进程对象等。我们都可以称他们为名字。较为常见的就是一些分布式服务框架中的服务地址列表。通过调用 zookeeper 提供的创建节点 API,能够很容易创建一个全局唯一的 path,这个 path 就可以作为一个名称。Name Service 已经是 zookeeper 内置的功能,你只要调用 zookeeper 的 API 就能实现。如调用 create 接口就可以很容易创建一个目录节点。

配置管理 (Configuration Management)

配置管理在分布式应用环境中非常常见,例如同一个应用系统需要多台 PC Server 运行,但是它们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样非常麻烦而且容易出错。

像这样的配置信息完全可以交给 zookeeper 来管理,将配置信息保存在 zookeeper 的某个节点中,所有用到该配置信息的节点都监控配置信息的状态,一旦配置信息发生变化,每个节点就会收到 zookeeper 的通知,然后从 zookeeper 获取新的配置信息应用到系统中。

集群管理 (Cluster Management)

有多台机器组成一个集群,必须有一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它机器必须知道,从而做出调整重新分配服务策略。同样当增加集群的服务能力时,就会增加一台或多台机器,同样也必须让“总管”知道。

zookeeper 可以帮我们在集群中选出这样的“总管”,同时,当这个总管挂掉,zookeeper 可以在剩余的机器上,选择另一个替代的总管。避免总管挂掉导致整个集群不可用。这就是 zookeeper 的选主功能。选主是 zookeeper 最重要的功能之一。

分布式锁 (Distribute Lock)

我们知道,单个节点上的多进程同步,可以使用互斥锁。进程要进入临界代码,先获得锁,然后才能操作临界区中的代码,没有获得锁的进程,必须等待锁。而在分布式系统中,情况就不同了。没有这么一个可以供多个节点上多个进程共享的锁。zookeeper 提供了一个解决方案,我们可以使用可排序的 znode 来实现。

原理很容易理解,我们可以先创建一个 /leader 的节点,然后让所有进程都尝试在 /leader 下创建 lock 临时节点,由上文得之,有且只有一个进程能创建成功。其他进程均创建失败。那么创建 lock 节点成功的进程,即表示获得了锁,可以做临界操作。以后,除非该进程主动释放锁,或者该进程崩溃,由于 lock 是临时节点,因此进程崩溃后,会自动删除,那么其他进程就又可以去竞争创建锁了。

总结

zookeeper 作为 Hadoop 项目中的一个子项目,在分布式系统中,承担着重要的角色,它解决的是分布式系统中遇到的一类典型问题,很多分布式系统中,都会用到 zookeeper 来保证系统运行。 如在 hadoop 集群管理中,它主要用来控制集群中的数据,如管理 hadoop 集群中的 NameNode,还有 Hbase 中 Master Election、Server 之间状态同步等。

本文介绍的 zookeeper 的基本知识,以及介绍了几个典型的应用场景。这些都是 zookeeper 的基本功能,最重要的是 zoopkeeper 提供了一套很好的分布式集群管理的机制,就是它这种基于层次型的目录树的数据结构,并对树中的节点进行有效管理,从而可以设计出多种多样的分布式的数据管理模型,而不仅仅局限于上面提到的几个常用应用场景。

参考