简介
相对于开发在一台计算机上运行的单个程序,如何让一个应用中多个独立的程序协同工作是一件非常困难的事情。
ZooKeeper(由雅虎研究院开发,托管到Apache软件基金会) 的设计保证了其健壮性,这就使得应用开发人员可以更多关注应用本身的逻辑,而不是协同工作上。ZooKeeper 从文件系统 API 得到启发,提供一组简单的 API,使得开发人员可以实现通用的协作任务,包括
- 选举主节点
- 管理组内成员关系、
- 管理元数据
- 等等
ZooKeeper 包括
- 一个主要提供 Java 语言应用开发库
- 一个 C 语言应用开发库
- 一个用 Java 实现的服务组件。
ZooKeeper 的服务组件运行在一组专用服务器之上,保证了高容错性和可扩展性。
你决定使用ZooKeeper来设计应用时,最好将应用数据和协同数据独立开。比如,网络邮箱服务的用户对自己邮箱中的内容感兴趣,但是并不关心由哪台服务器来处理特定邮箱的请求。
在这个例子中,邮箱内容就是应用数据,而从邮箱到某一台邮箱服务器之间的映射关系就是 协同数据(或称元数据)。整个 ZooKeeper 服务所管理的就是后者 。
关于 ZooKeeper 的系统功能的主线是:在分布式系统中协作多个任务(一个协作任务是指一个包含多个进程的任务)。这个任务可以是为了协作或者是为了管理竞争。
在 主-从(master-worker)工作模式中,主节点分配工作,从节点接受工作,这就是 协作 。
竞争 则不同。它指的是两个进程不能同时处理工作的情况,一个进程必须等待另一个进程。例如,多个节点都想称为主节点。
要实现主-从模式的系统,我们必须解决以下三个关键问题:
关键问题 | 描述 |
---|---|
主节点崩溃 | 如果主节点发送错误并失效,系统将无法分配新的任务或重新分配已失败的任务。 |
从节点崩溃 | 如果从节点崩溃,已分配的任务将无法完成。 |
通信故障 | 如果主节点和从节点之间无法进行信息交换,从节点将无法得知新任务分配给它。 |
主节点失效面对的问题:
- 备份主节点需要 恢复/获得 主节点崩溃前的状态。
- 备份主节点可能会误以为主节点崩溃,从而导致出现两个主节点。
- 从节点可能误认为主节点崩溃,从而与备份主节点建立主-从关系(脑裂)。
从节点失效面对的问题:
- 主节点要具有检查从节点崩溃的能力。
- 原从节点崩溃后(未能完成工作),主节点要能重新派发工作给新的可用的从节点。
- 从节点崩溃时,如果执行的部分工作以生效,整个系统需要有“回滚”的能力。
通信故障面对的问题:
- 主从断开连接,可能会导致一个工作被两个节点执行(一个工作被执行多次)。
可靠的 主-从 架构的要求:
主节点选举
: 这是关键的一步,使得主节点可以给从节点分配任务。
崩溃检测
: 主节点必须具有检测从节点崩溃或失去连接的能力。
组成员关系管理
: 主节点必须具有知道哪一个从节点可以执行任务的能力。
元数据管理
: 主节点和从节点必须具有通过某种可靠的方式来保存分配状态和执
行状态的能力。
一个在分布式计算领域非常著名的定律,FLP(由其作者命名:Fischer,Lynch,Patterson)或 CAP 定律,表示一致性(Consistency)、可用性(Availability)和分区容错性(Partition-tolerance)。该定律指出,一个分布式系统无法同时满足这三种属性。
ZooKeeper 的作者们尽可能满足 一致性 和 可用性,牺牲了一定的分区容错性。
ZooKeeper 基础
ZooKeeper 操作和维护一个小型的数据节点,这些节点被称为znode,采用类似于文件系统的层级树状结构进行管理(znode 通过父-子关系组成一个树状结构)。znode 可以包含数据,也可以不包含数据(字节数组)。
ZooKeeper 的 API 暴露了以下方法:
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 中的某一部分数据。
ZooKeeper 客户端连接到 ZooKeeper 服务,通过 API 调用来建立会话 (session)。
znode 的节点类型:
znode 节点可以是 持久节点,还可以是 临时节点 。持久节点必须被 delete 删除,临时节点在客户端断开连接后就被删除。
持久节点用于保存数据。
临时节点的存在,暗示着它所对应的主机或从机的 存在/正常 。
临时节点不允许拥有子节点。
一个 znode 还可以设置为 有序节点 。一个有序 znode 节 点被分配唯一个单调递增的整数。当创建有序节点时,一个序号会被追加到路径之后。
因此,znode 一共有 4 种类型:
- 持久的
- 临时的
- 持久有序的
- 临时有序的
为了替换客户端的轮询,我们选择了基于 通知 的机制:客户端向 ZooKeeper 注册需要接收通知的 znode,通过对 znode 设置 监视点 来接收通知。
监视点是一个单次触发的操作,即监视点会触发一个通知。为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。
通知机制的一个重要保障是,对同一个 znode 的操作,先向客户端传送通知,然后再对该节点进行变更。
客户端可以设置多种监视点,如:
- 监控 znode 的数据变化
- 监控 znode 子节点的变化
- 监控 znode 的创建或删除
每一个 znode 都有一个版本号,它随着每次数据变化而自增。两个 API 操作可以有条件地执行:setData 和 delete。这两个调用以版本号作为转入参数,只有当转入参数的版本号与服务器上的版本号一致时调用才会成功。使用版本好可以阻止并行操作的不一致性问题。
ZooKeeper 服务器端运行于两种模式下:独立模式(standalone)和 仲裁模式(quorum)。
独立模式,有一个单独的服务器,ZooKeeper状态无法复制。
在仲裁模式下,具有一组 ZooKeeper 服务器,我们称为 ZooKeeper 集合(ZooKeeper ensemble),
它们之前可以进行状态的复制,并同时为服务于客户端的请求。
在仲裁模式中,由于 ZooKeeper 集群中的每个个体需要同步一切数据,所以为了提高效率,假设,一共有 5 个 ZooKeeper 服务器,但 法定人数 为3个,这样,只要任何 3 个服务器保存了数据,客户端就可以继续,而其他两个服务器最终也将捕获到数据,并保存数据。
在对 ZooKeeper 集合执行任何请求前,一个客户端必须先与服务建立会话。客户端提交给 ZooKeeper 的所有操作均关联在一个会话上。当一个会话因某种原因而中止时,在这个会话期间创建的临时节点将会消失。
对于 ZooKeeper 集群(冲裁模式下),ZooKeeper 客户端库透明地转移一个会话到不同的服务器。
会话提供了顺序保障,这就意味着同一个会话中的请求会以 FIFO 顺序执行。
一个会话的主要可能状态大多是简单明了的:CONNECTING、CONNECTED、CLOSED 和 NOT_CONNECTED。
当客户端与 ZooKeeper 服务器断开连接或者无法收到服务器的响应时,它就会转换回 CONNECTING 状态并尝试发现其他 ZooKeeper 服务器。如果可以发现另一个服务器或重连到原来的服务器,当服务器确认会话有效后,状态又会转换回 CONNECTED 状态。否则,它将会声明会话过期,然后转换到 CLOSED 状态。
主-从模式的模型中包括三个角色:
角色 | 说明 |
---|---|
主节点 | 主节点负责监视新的从节点和任务,分配任务给可用的从节点。 |
从节点 | 从节点会通过系统注册自己,以确保主节点看到它们可以执行任务,然后开始监视新任务。 |
客户端 | 客户端创建新任务并等待系统的响应。 |
各种角色
抢先创建 /master 节点(临时节点)的“人”就是主节点角色。
要抢但未抢到创建 /master 节点的“人”会监视 /master 节点的消失,以便自己再次创建 /master,它就备份主节点角色。
在 /workers
节点下创建子节点的“人”就是从节点,它会在创建的节点中留下自己的信息(“联系方式”)。主节点会监视 /workers
节点下的内容变动,以感知从节点的出现和消失。
从节点会在创建 /assign
下创建自己对应的子节点,并等待客户端其中添加数据(分配工作)。
客户端在 /tasks
下创建子节点,来表示自己有个任务需要有人来完成。主节点会监视这个节点,以随时返现有人有任务请求。
主节点会将任务放入某个“人”的 /assign
对应的目录中,以表示让他完成这个任务。
执行任务的从机完成任务后,会向 /tasks
节点对应任务的节点下下添加一个子节点表示已完成,而客户端会监控并感知到任务完成。
ZooKeeper 的 API
ZooKeeper 使用了大量的第三方库。为此需要设置 CLASSPATH。
Windows 上
call ZOOKEEPER_HOME/bin/zkEnv.cmd
Linux 上
. ZOOKEEPER_HOME/bin/zkEnv.sh
ZooKeeper 的 API 都是围绕 ZooKeeper 的 句柄(handle)而构建,每个 API 调用都需要传递这个句柄。这个句柄代表了与 ZooKeeper 之间的一个会话。只要会话还存活着,这个句柄就始终有效。
... [myid:] - INFO [...] - Server environment:java.io.tmpdir=C:\Users\ADMINI~1\AppData\Local\Temp\
... [myid:] - INFO [...] - Server environment:java.compiler=
... [myid:] - INFO [...] - Server environment:os.name=Windows 7
... [myid:] - INFO [...] - Server environment:os.arch=amd64
... [myid:] - INFO [...] - Server environment:os.version=6.1
... [myid:] - INFO [...] - Server environment:user.name=Administrator
... [myid:] - INFO [...] - Server environment:user.home=C:\Users\Administrator
... [myid:] - INFO [...] - Server environment:user.dir=D:\Program Files\zookeeper-3.4.12\bin
... [myid:] - INFO [...] - tickTime set to 2000
... [myid:] - INFO [...] - minSessionTimeout set to -1
... [myid:] - INFO [...] - maxSessionTimeout set to -1
... [myid:] - INFO [...] - Using org.apache.zookeeper.server.NIOServerCnxnFactory as server connection factory
... [myid:] - INFO [...] - bindingto port 0.0.0.0/0.0.0.0:2181
... [myid:] - INFO [...] - Accepted socket connection from /127.0.0.1:64763
... [myid:] - INFO [...] - Client attempting to establish new session at /127.0.0
.1:64763
... [myid:] - INFO [...] - Creating new log file: log.9
... [myid:] - INFO [...] - Established session 0x100003094e20000 with negotiated timeout 15000 for client /127.0.0.1:64763
... [myid:] - WARN [...] - Exception causing close of session 0x100003094e20000: 远
程主机强迫关闭了一个现有的连接。
2018-06-20 19:58:09,827 [myid:] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:21
81:NIOServerCnxn@1040] - Closed socket connection for client /127.0.0.1:64763 wh
ich had sessionid 0x100003094e20000
2018-06-20 19:58:21,166 [myid:] - INFO [SessionTracker:ZooKeeperServer@354] - E
xpiring session 0x100003094e20000, timeout of 15000ms exceeded
2018-06-20 19:58:21,166 [myid:] - INFO [ProcessThread(sid:0 cport:2181)::PrepRe
questProcessor@487] - Processed session termination for sessionid: 0x100003094e2
0000