zookeeper集群搭建与配置

Zookeeper 分布式服务框架是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。

数据结构

Zookeeper 会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统

Zookeeper 这种数据结构有如下这些特点:

  • 每个子目录项如 NameService 都被称作为 znode,这个 znode 是被它所在的路径唯一标识,如 Server1 这个 znode 的标识为 /NameService/Server1
  • znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL 类型的目录节点不能有子节点目录
  • znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据
  • znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,Zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了
  • znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2
  • znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基于这个特性实现的

节点类型

Zookeeper 有四种节点类型:

PERSISTENT

持久化节点,数据落地到Zookeeper文件系统中会一直存在,不会因为客户端连接关闭或会话失效而被删除。持久化节点下面可以创建子节点。

1
2
[zk: localhost:2181(CONNECTED) 11] create /test/node hello
Created /test/node

这个节点将会是持久化的,断开连接后重新进入依然存在。

PERSISTENT_SEQUENTIAL

有序的持久化节点,基本特性和持久化节点一致,每次创建节点时,zookeeper会在节点名称后面加上一个顺序的流水号,作为新节点名称。

1
2
[zk: localhost:2181(CONNECTED) 12] create -s /test/node2 hello
Created /test/node20000000001

创建的是/test/node2 节点,实际zookeeper存储的是/test/node2,zookeeper自动会在提交的节点后面加上一串流水0000000001,当然这个流水号是唯一的。这个节点也是持久化的,断开连接后重新进入依然存在。

EPHEMERAL

临时节点,数据只是临时性的,当连接关闭或会话失效后,数据节点被Zookeeper自动删除,而且临时节点下面不能创建子节点。

1
2
[zk: localhost:2181(CONNECTED) 14] create -e /test/node3 hello
Created /test/node3

这个节点是临时的,断开连接后,将会被自动删除。

EPHEMERAL_SEQUENTIAL

有序临时节点,基本特性和临时节点一致,每次创建节点时,zookeeper会在节点名称后面加上一个顺序的流水号,作为新节点名称。

1
2
[zk: localhost:2181(CONNECTED) 17] create -e -s /test/node4 hello
Created /test/node40000000004

创建的是/test/node4 节点,实际zookeeper存储的是/test/node40000000004,zookeeper自动会在提交的节点后面加上一串流水0000000004,当然这个流水号是唯一的。这个节点是临时的,断开连接后,将会被清除。

ZooKeeper集群搭建

下载
wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.6.tar.gz

配置文件
mv conf/zoo_sample.cfg conf/zoo.cfg
默认配置文件内容如下

1
2
3
4
5
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/zookeeper/data/
clientPort=2181

  • tickTime 这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每隔 tickTime 时间就会发送一个心跳,单位为毫秒。
  • initLimit 用来配置 Zookeeper Follower客户端初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 5*2000 ms=10s
  • syncLimit 标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 2*2000 ms=4s
  • dataDir Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
  • clientPort 这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。

编辑zoo.cfg,新增如下

1
2
3
server.1=192.168.62.147:2888:3888
server.2=192.168.62.148:2888:3888
server.3=192.168.62.149:2888:3888

其中,server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。

在cfg指定的data目录下新建myid,并将对应的server序号写入

1
2
3
echo 1 > myid # 192.168.62.147
echo 1 > myid # 192.168.62.148
echo 1 > myid # 192.168.62.149

这个文件里面只有一个数据就是 A 的值,Zookeeper 启动时会读取这个文件,拿到里面的数据与 zoo.cfg 里面的配置信息比较从而判断到底是那个 server。

启动 bin/zkServer.sh start
客户端连接 bin/zkCli.sh

查看角色

1
2
3
4
[root@ZK-3 bin]# ./zkServer.sh status
JMX enabled by default
Using config: /data/zookeeper-3.4.6/conf/zoo.cfg
Mode: leader

测试

选择一节点,比如192.168.62.148上执行 bin/zkCli.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[zk: 192.168.62.148:2181(CONNECTED) 5] create /mytest test
Created /mytest
[zk: 192.168.62.148:2181(CONNECTED) 6] ls /
[mytest, zookeeper]
[zk: 192.168.62.148:2181(CONNECTED) 7] get /mytest
test
cZxid = 0x100000002
ctime = Mon Jul 27 22:47:27 CST 2015
mZxid = 0x100000002
mtime = Mon Jul 27 22:47:27 CST 2015
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0

其中

  • czxid 节点创建时的zxid.
  • ctime 节点创建时的时间戳.
  • mzxid 节点最新一次更新发生时的zxid.
  • mtime 节点最新一次更新发生时的时间戳.
  • cversion 其子节点的更新次数.
  • dataVersion 节点数据的更新次数.
  • aclVersion 节点ACL(授权信息)的更新次数.
  • ephemeralOwner 如果该节点为ephemeral节点, ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是ephemeral节点, ephemeralOwner值为0.
  • dataLength 节点数据的字节数.
  • numChildren 子节点个数.

在其他节点上,比如192.168.62.149上也能看到mytest目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[zk: localhost:2181(CONNECTED) 1] ls /
[mytest, zookeeper]
[zk: localhost:2181(CONNECTED) 2] get /mytest
test
cZxid = 0x100000002
ctime = Mon Jul 27 22:47:27 CST 2015
mZxid = 0x100000002
mtime = Mon Jul 27 22:47:27 CST 2015
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0

日志默认在bin/zookeeper.out

监控

可以使用 echo command | nc ip port 来监控zk的状态

  • echo stat | netcat 127.0.0.1 2181 查看哪个节点被选择作为follower或者leader
  • echo ruok | netcat 127.0.0.1 2181 测试是否启动了该Server,若回复imok表示已经启动。
  • echo dump | netcat 127.0.0.1 2181 列出未经处理的会话和临时节点。
  • echo kill | netcat 127.0.0.1 2181 关掉server
  • echo conf | netcat 127.0.0.1 2181 输出相关服务配置的详细信息。
  • echo cons | netcat 127.0.0.1 2181 列出所有连接到服务器的客户端的完全的连接/会话的详细信息。
  • echo envi | netcat 127.0.0.1 2181 输出关于服务环境的详细信息(区别于 conf 命令)。
  • echo reqs | netcat 127.0.0.1 2181 列出未经处理的请求。
  • echo wchs | netcat 127.0.0.1 2181 列出服务器 watch 的详细信息。
  • echo wchc | netcat 127.0.0.1 2181 通过 session 列出服务器 watch 的详细信息,它的输出是一个与 watch 相关的会话的列表。
  • echo wchp | netcat 127.0.0.1 2181 通过路径列出服务器 watch 的详细信息,它输出一个与 session 相关的路径。

基本API

这里简要介绍几个接口方法。

String create(final String path, byte data[], List<ACL> acl,CreateMode createMode)
创建一个给定的目录节点path, 并给它设置数据,CreateMode 标识上述的四种ZK节点类型,即

  • PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;
  • PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;
  • EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;
  • EPHEMERAL_SEQUENTIAL:临时自动编号节点

Stat exists(String path, boolean watch)
判断某个 path 是否存在,并设置是否监控这个目录节点,这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher,exists方法还有一个重载方法,可以指定特定的 watcher

Stat exists(String path, Watcher watcher)
重载方法,这里给某个目录节点设置特定的 watcher,Watcher 在 ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的 Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应

void delete(String path, int version)
删除 path 对应的目录节点,version 为 -1 可以匹配任何版本,也就删除了这个目录节点所有数据

List<String> getChildren(String path, boolean watch)
获取指定 path 下的所有子目录节点,同样 getChildren方法也有一个重载方法可以设置特定的 watcher 监控子节点的状态

Stat setData(String path, byte[] data, int version)
给 path 设置数据,可以指定这个数据的版本号,如果 version 为 -1 怎可以匹配任何版本

byte[] getData(String path, boolean watch, Stat stat)
获取这个 path 对应的目录节点存储的数据,数据的版本等信息可以通过 stat 来指定,同时还可以设置是否监控这个目录节点数据的状态

void addAuthInfo(String scheme, byte[] auth) 客户端将自己的授权信息提交给服务器,服务器将根据这个授权信息验证客户端的访问权限。

Stat setACL(String path, List<ACL> acl, int version)
给某个目录节点重新设置访问权限,需要注意的是 Zookeeper 中的目录节点权限不具有传递性,父目录节点的权限不能传递给子目录节点。目录节点 ACL 由两部分组成:perms 和 id。Perms 有 ALL、READ、WRITE、CREATE、DELETE、ADMIN 几种;而 id 标识了访问目录节点的身份列表,默认情况下有以下两种:ANYONE_ID_UNSAFE = new Id(“world”, “anyone”) 和 AUTH_IDS = new Id(“auth”, “”)。分别表示任何人都可以访问和创建者拥有访问权限。

List<ACL> getACL(String path, Stat stat)
获取某个目录节点的访问权限列表

完整API请参考官方文档

简单操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package zookeeper;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.CreateMode;
public class Basic {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("192.168.62.147:2181",5000,new Watcher(){
public void process(WatchedEvent event){
// no process
}
});
if(zk.exists("/apitest", false)!=null)
System.err.println("node exists");
zk.create("/apitest","hello".getBytes(),Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
// System.out.println("create /apitest done");
zk.create("/apitest/subnode","hello".getBytes(),Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
// System.out.println("create /apitest/subnode done");
// System.out.print("Get /apitest:");
System.out.println(new String(zk.getData("/apitest",false,null)));
// System.out.print("getChildren of /apitest:");
System.out.println(zk.getChildren("/apitest",false));
// System.out.print("getChildren of / :");
System.out.println(zk.getChildren("/",false));
// System.out.print("Data of /apitest:");
System.out.println(new String(zk.getData("/apitest", false, null)));
zk.setData("/apitest", "modify data".getBytes(), -1);
// System.out.print("Data after modify:");
System.out.println(new String(zk.getData("/apitest", false, null)));
zk.delete("/apitest/subnode", -1);
// System.out.println("del /apitest/subnode done");
zk.delete("/apitest",-1);
// System.out.println("del /apitest done");
zk.close();
System.out.println("close");
}
}

结果

1
2
3
4
5
6
7
8
9
10
create /apitest done
create /apitest/subnode done
Get /apitest:hello
getChildren of /apitest:[subnode]
getChildren of / :[two, apitest, test, mytest, zookeeper]
Data of /apitest:hello
Data after modify:modify data
del /apitest/subnode done
del /apitest done
close

如果您觉得这篇文章对您有帮助,不妨支持我一下!