ZooKeeper
基础教程
1. 概述
1.1 简介
Zookeeper
是一个基于Java的分布式协调服务的开源框架。主要用来解决分布式集群中应用系统的一致性问题,例如怎样避免同时操作同一数据造成脏读的问题。ZooKeeper
本质上是一个分布式的小文件存储系统。提供基于类似于文件系统的目录树方式的数据存储,并且可以对树中的节点进行有效管理。从而用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。诸如:统一命名服务、分布式配置管理、分布式消息队列、分布式锁、分布式协调等功能。
1.2 集群
ZooKeeper
集群包含两种角色,Leader和Follower。
Leader
Zookeeper
集群工作的核心,事务请求(写操作)的唯一调度和处理者,保证集群事务处理的顺序性;集群内部各个服务器的调度者。 对于
create,setData,delete
等有写操作的请求,则需要统一转发给 leader处理,leader需要决定编号、执行操作,这个过程称为一个事务。Follower
处理客户端非事务(读操作)请求,转发事务请求给 Leader; 参与集群 Leader 选举投票。 此外,针对访问量比较大的
ZooKeeper
集群,还可新增观察者角色。
Observer:
观察者角色,观察
Zookeeper
集群的最新状态变化并将这些状态同步过 来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给Leader服务器进行处理。不会参与任何形式的投票只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。
1.3 集群搭建
集群通常由2n+1台 servers 组成。这是因为为了保证 Leader 选举(基于Paxos
算法的实现)能过得到多数的支持,所以ZooKeeper
集群的数量一般为奇数。
Zookeeper
运行需要java
环境,所以需要提前安装 。对于安装 leader+follower 模式的集群,大致过程如下
配置主机名称到IP地址映射配置
修改
ZooKeeper
配置文件(/conf/zoo.cfg
文件)配置data暂存目录,各个服务器地址ip
1
2
3
4
5
6
7
8
9...
dataDir=/usr/local/zookeeper-3.4.14/zkData
clientPort=2181
server.0=192.168.10.1:2888:3888
server.1=192.168.10.2:2888:3888
server.2=192.168.10.3:2888:3888
...server.A=B:C:D
解释:A:其中 A 是一个数字,表示这个是服务器的编号;
B:是这个服务器的IP地址;
C:
Leader
选举的端口;D:
Zookeeper
服务器之间的通信端口。远程复制分发安装文件
设置
myid
在 上一步
dataDir
指定的目录下,创建myid
文件。在对应的服务器上写入对应序号。比如
192.168.10.1
对应server.0
,那么在myid
中写入0即可。启动集群
启动命令:
zkServer.sh start
停止命令:
zkServer.sh stop
重启命令:
zkServer.sh restart
查看集群结点状态:
zkServer.sh status
2 Shell操作
bin/zkCli.sh
命令进入命令行界面。
2.1 创建结点
ZooKeeper
中的结点类型分为永久节点和临时结点(-e)。
1 | create [-s] [-e] path data acl |
其中,-s 或-e 分别指定节点特性,顺序或临时节点,若不指定,则表示持 久节点;acl
用来进行权限控制。
例子:
创建顺序节点,结点值为123:
1 | create -s /test 123 |
创建临时结点
1 | create -e /test-tmp 123 |
创建永久节点:
1 | create /test-per 123p |
2.2 读取节点
与读取相关的命令有`ls`命令和 `get` 命令,`ls` 命令可以列出 `Zookeeper `指 定节点下的所有子节点,只能查看指定节点下的第一级的所有子节点;`get` 命令 可以获取 `Zookeeper` 指定节点的数据内容和属性信息。
查看根目录下所有结点:
1 | [zk: localhost:2181(CONNECTED) 0] ls / |
获取/nefu
结点的信息
1 | [zk: localhost:2181(CONNECTED) 3] get /nefu |
2.3 更新与删除结点
2.3.1 更新结点
set path data [version]
data 就是要更新的新内容,version 表示数据版本。
1 | [zk: localhost:2181(CONNECTED) 5] set /nefu ilovenefu |
2.3.2 删除节点
delete path [version]
注意:若删除节点存在子节点,那么无法删除该节点,必须先删除子节点,再删除父节点。
1 | Rmr path |
3. ZooKeeper
数据模型
ZooKeeper
的数据模型,在结构上和标准文件系统的非常相似,拥有一个层次的命名空间,都是采用树形层次结构,ZooKeeper
树中的每个节点被称为一个Znode
。和文件系统的目录树一样,ZooKeeper
树中的每个节点可以拥有子节点。
图中的每个节点称为一个Znode
。每个Znode
由 3 部分组成:
① stat:此为状态信息, 描述该 Znode
的版本, 权限等信息
② data:与该 Znode
关联的数据
③ children:该Znode
下的子节点
Znode
有两种,分别为临时节点和永久节点。
节点的类型在创建时即被确定,并且不能改变。
临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然可以也可以手动删除。临时节点不允许拥有子节点。
永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删 除操作的时候,他们才能被删除。
Znode
还有一个序列化的特性,如果创建的时候指定的话,该Znode
的名字 后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一 的,这样便会记录每个子节点创建的先后顺序。它的格式为“%10d”(10 位数字, 没有数值的数位用 0 补充,例如“0000000001”)。
4. Watcher
ZooKeeper 提供了分布式数据发布/订阅功能,一个典型的发布/订阅模型系统定义了一种一对多的订阅关系,能让多个订阅者同时监听某一个主题对象,当 这个主题对象自身状态变化时,会通知所有订阅者,使他们能够做出相应的处理。
ZooKeeper 中,引入了 Watcher 机制来实现这种分布式的通知功能。 ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些事件触 发了这个 Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
触发事件种类很多,如:节点创建,节点删除,节点改变,子节点改变等。
总的来说可以概括 Watcher 为以下三个过程:客户端向服务端注册 Watcher、 服务端事件发生触发 Watcher、客户端回调 Watcher 得到触发事件情况。
5. Java API编程
首先导入pom
依赖
1 |
|
简单的连接集群和结点操作
1 | import org.apache.zookeeper.*; |
6. ZooKeeper典型应用
6.1 数据发布与订阅(配置中心)
发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。
应用在启动的时候会主动来获取一次配置,同时,在节点上注册一个 Watcher, 这样一来,以后每次配置有更新的时候,都会实时通知到订阅的客户端,从来达 到获取最新配置信息的目的。
比如: 分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存放在 ZK 的一些指定节点,供各个客户端订阅使用。
注意:适合数据量很小的场景,这样数据更新可能会比较快。
6.2 命名服务(Naming Service)
在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取 资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提 供的服务地址,远程对象等等——这些我们都可以统称他们为名字(Name)。其 中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用 ZK 提供的 创建节点的 API,能够很容易创建一个全局唯一的 path,这个 path 就可以作为 一个名称。
阿里巴巴集团开源的分布式服务框架 Dubbo
中使用 ZooKeeper
来作为其命 名服务,维护全局的服务地址列表。
6.3 分布式锁
分布式锁,这个主要得益于 ZooKeeper 保证了数据的强一致性。锁服务可以 分为两类,一个是保持独占,另一个是控制时序。
所谓保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成 功获得这把锁。通常的做法是把 zk 上的一个 znode 看作是一把锁,通过 create znode 的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功 创建的那个客户端也即拥有了这把锁。
控制时序,就是所有试图来获取这个锁的客户端,最终都是会被安排执行, 只是有个全局时序了。做法和上面基本类似,只是这里 /distribute_lock 已经 预先存在,客户端在它下面创建临时有序节点(这个可以通过节点的属性控制: CreateMode.EPHEMERAL_SEQUENTIAL 来指定)。Zk 的父节点(/distribute_lock) 维持一份 sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局 时序。