一、简介
Zookeeper在生产环境搭建为集群环境才有意义,这是基于Zookeeper的ZAB算法的实现方式,一般是使用奇数台机器做集群,方便进行leader选举和事务提交。当然在本地没有条件的情况下可以使用单台机器搭建伪集群环境或单机环境。
二、准备工作
首先是在官网下载Zookeeper安装包,在Apache官网选择其中一个Zookeeper镜像网站进行下载:http://mirror.bit.edu.cn/apache/zookeeper/,选择最新的稳定版本3.4.13。
Zookeeper是java语言开发的,因此本地环境需要配置好Java环境。
Zookeeper可以适用于Windows系统和linux系统,在bin目录下有对应的cmd脚本和sh脚本。
三、配置
1、解压
将下载的安装包解压到目标目录,因为为了简单没有使用物理机器搭建集群环境,直接在单台机器上搭建,因此需要将程序包复制两份,搭建一个3台机器的集群环境。
2、配置
进入\zookeeper-3.4.13\conf目录,可以看到名称为zoo_sample.cfg的文件,将文件名改为zoo.cfg,打开文件主要配置如下:
#服务器之间或客户端与服务器之间维持心跳的时间间隔
tickTime=2000
#集群中的follower服务器与leader服务器之间初始连接时能容忍的最多心跳数
initLimit=10
#集群中的follower服务器与leader服务器之间请求和应答之间能容忍的最多心跳数
syncLimit=5
#Zookeeper保存数据的目录
dataDir=D:\\Program Files\\zookeeper-3.4.13\\data
Zookeeper保存日志的目录,如果不配置默认和data在同一目录
dataLogDir=D:\\Program Files\\zookeeper-3.4.13\\log
#客户端连接 Zookeeper 服务器的端口
clientPort=2181
#集群服务器名称.服务器编号=各集群服务器地址:Leader和Follower通信端口,选举端口
#集群有几台机器就配置几条信息
server.1=192.168.1.183:2777:3777
server.2=192.168.1.183:3888:4888
server.3=192.168.1.183:5999:6999
在数据目录D:\Program Files\zookeeper-3.4.13\data下新建文件myid,文件内容就是zoo.cfg文件中配置的服务器编号,比如当前服务器是server.2=192.168.1.183:3888:4888,那么文件内容就是:2。
另外由于这里使用单台机器搭建集群所以clientPort每个Zookeeper实例不同,否则会导致端口冲突。
四、启动集群
可以通过命令或者直接运行bin目录下的服务端脚本来启动Zookeeper进程。
1、命令启动:在命令行进入bin目录通过zkServer.cmd命令启动。
2、直接进入bin目录双击zkServer.cmd文件启动。
3、可以在命令行通过telnet命令验证启动是否成功,比如输入telnet 192.168.1.183 2181,然后输入stat命令查看:
这台机器是follower角色,以此分别查看其他机器的状态。如果是单击模式的话,看到的模式回收standalone。
五、客户端脚本
同样可以通过命令行或直接运行bin目录下的zkCli.cmd文件两种方式启动Zookeeper客户端。
1、命令行:
这样连接的是同一目录下的Zookeeper服务器,如果是连接指定ip端口的机器可以通过zkCli.cmd –server ip:port命令连接。
2、在bin目录下直接双击zkCli.cmd文件启动客户端
连接成功后显示如下:
1)查看节点信息
可以通过ls、ls2查看指定路径下的节点信息,第一次部署的Zookeeper集群,默认在根节点”/”下面有一个/zookeeper的保留节点。
比如ls /查看根目录下的节点:
2)获取指定节点的数据内容和属性信息
使用get path [watch],比如:
get /zk-book
其中333是数据内容,cZxid是创建该节点的事务ID,mZxid是最后一次更新该节点的事务ID,dataVersion是记录的数据版本。
3)创建节点
使用create [-s] [-e] path data acl命令创建节点,其中-s是指创建带整型序列标示的节点,-e是创建临时节点,不加-s和-e是创建持久节点,acl是节点的权限控制。
比如:
create –s /zk-test-c aaa
4)更新节点数据
使用set命令可以更新指定节点的数据内容set path data [version],其中的version是指本次操作是基于ZNode的哪一个数据版本进行的,比如:
set /zk-book 666
5)删除节点
使用delete path [version]命令可以删除Zookeeper上的指定节点。比如:
delete /zk-book
该命令删除的节点下面必须没有子节点存在,否则会删除失败,比如:
create /zk-book 123
create /zk-book/zk-child 111
delete /zk-book
六、Java客户端API使用
Java代码使用的Zookeeper包可以从maven仓库下载https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper,不同版本的Zookeeper包使用的log4j和slf4j版本不同。
1、创建会话
客户端可以通过创建一个Zookeeper实例来连接Zookeeper服务器。Zookeeper类有4种构造方法:
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher);
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly);
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd);
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly);
其中connectString是至Zookeeper服务器列表,由host:port组成用逗号分隔,比如本地搭建集群,192.168.1.183:2181, 192.168.1.183:2182, 192.168.1.183:2183,这样就为客户端指定了3台服务器地址,还可以指定客户端连接上Zookeeper后的根目录,方法是在host:port之后添加这个根目录,比如,192.168.1.183:2181, 192.168.1.183:2182, 192.168.1.183:2183/zk-test,这样该客户端连接上服务端之后的操作都基于该目录。
sessionTimeout是会话的超时时间,是一个以毫秒为单位的整型。在一个会话周期内,Zookeeper客户端和服务端通过心跳机制来维持会话,一旦sessionTimeout时间内没有进行有效的心跳检测,会话就会失效。
watcher是一个实现了watcher接口的类对象,作为默认的Watcher事件通知处理器。如果设置为mull表示不需要对Watcher事件做处理。
canBeReadOnly用于标示当前会话是否支持只读模式。
sessionId和sessionPasswd分别代表会话ID和会话秘钥,这两个参数能够确定唯一的一个会话,同时客户端使用这两个参数能够实现客户端会话的复用,从而达到恢复会话的目。可以通过调用Zookeeper实例对象的public long getSessionId();和public byte[] getSessionPasswd()来获取,然后在下次创建Zookeeper实例时传入。
在构造Zookeeper客户端和服务端的连接时,需要注意,这是一个异步的过程,即服务端在收到客户端的请求后会立刻返回,此时服务端可能还没有建立一个真正的会话,在会话的生命周期中还处于connecting状态。当该会话真正创建完毕后,服务端会向会话对应的客户端返回一个事件通知,客户端只有获取到这个通知之后,才算真正建立了会话。因此在创建Zookeeper实例对象后,最好阻塞当前线程,等待获取到服务端的事件通知后再继续执行。
package com.exp.execrise;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher.Event.KeeperState;
public class ZookeeperCli implements Watcher
{
//等待服务端的会话建立完成的事件通知
private static CountDownLatch connected=new CountDownLatch(1);
private static final String CONNECT_STRING=
"192.168.1.183:2181,192.168.1.183:2182,192.168.1.183:2183/";
public static void main(String[] args) throws IOException, KeeperException, InterruptedException
{
ZooKeeper zk=new ZooKeeper(CONNECT_STRING, 5000, new ZookeeperCli());
try
{
connected.await();
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println("Zookeeper session established.");
System.out.println(zk.getChildren("/", null));
}
/*
* 实现服务端的事件通知
* @see org.apache.zookeeper.Watcher#process(org.apache.zookeeper.WatchedEvent)
*/
@Override
public void process(WatchedEvent event)
{
//服务端建立会话连接后,唤醒客户端主线程
System.out.println("Received watched event:"+event);
if (KeeperState.SyncConnected==event.getState())
{
connected.countDown();
}
}
}
通过sessionID和passwd复用会话。
package com.exp.execrise;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher.Event.KeeperState;
public class ZookeeperCliWithSidPasswd implements Watcher
{
//等待服务端的会话建立完成的事件通知
private static CountDownLatch connected=new CountDownLatch(1);
private static final String CONNECT_STRING=
"192.168.1.183:2181,192.168.1.183:2182,192.168.1.183:2183/";
public static void main(String[] args) throws IOException, InterruptedException
{
ZooKeeper zk=new ZooKeeper(CONNECT_STRING, 5000, new ZookeeperCliWithSidPasswd());
//等待服务端通知连接构造完成
connected.await();
long sessionId=zk.getSessionId();
byte [] passwd=zk.getSessionPasswd();
zk=new ZooKeeper(CONNECT_STRING, 5000, new ZookeeperCliWithSidPasswd(),sessionId,passwd);
zk=new ZooKeeper(CONNECT_STRING, 5000, new ZookeeperCliWithSidPasswd(),1l,"passwd".getBytes());
//挂起主线程,等待客户端和服务端建立连接
TimeUnit.MILLISECONDS.sleep(Integer.MAX_VALUE);
}
@Override
public void process(WatchedEvent event)
{
//服务端建立会话连接后,唤醒客户端主线程
System.out.println("Received watched event:"+event);
if (KeeperState.SyncConnected==event.getState())
{
connected.countDown();
}
}
}
2、创建节点
Zookeeper不支持递归创建节点,如果节点已经存在创建会抛出异常。目前Zookeeper的节点内容只支持字节数组,因此节点内容的序列化和反序列化需要自己实现。可以通过同步或异步的方式创建节点,方法如下:
public String create(String path, byte[] data, List acl, CreateMode createMode);
public void create(String path, byte[] data, List acl, CreateMode createMode, AsyncCallback.StringCallback cb, Object ctx);
其中path是要创建的节点的路径
data是节点创建后保存的数据内容
acl是节点的访问控制列表
createMode是节点的类型,是一个枚举类型,分为PERSISTENT:持久,PERSISTENT_SEQUENTIAL:持久顺序,EPHEMERAL:临时,EPHEMERAL_SEQUENTIAL:临时顺序。
cb是一个异步回调函数,需要实现StringCallback接口,主要是重写processResult()方法,当服务端节点创建完毕后,客户端就会自动调用这个方法。
ctx是在执行回调方法的时候使用,用于传递一个对象,比如上下文信息。
1)使用同步方法创建节点
package com.exp.execrise;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
public class ZookeeperCreateZNodeSync implements Watcher
{
//等待服务端的会话建立完成的事件通知
private static CountDownLatch connected=new CountDownLatch(1);
private static final String CONNECT_STRING=
"192.168.1.183:2181,192.168.1.183:2182,192.168.1.183:2183/";
public static void main(String[] args) throws IOException, InterruptedException, KeeperException
{
ZooKeeper zk=new ZooKeeper(CONNECT_STRING, 5000, new ZookeeperCreateZNodeSync());
//等待服务端通知连接构造完成
connected.await();
String path1=zk.create("/zk-test-persisent", "123".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("Success to create znode:"+path1);
String path2=zk.create("/zk-test-persisent", "123".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
System.out.println("Success to create znode:"+path2);
String path3=zk.create("/zk-test-ephemeral", "123".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println("Success to create znode:"+path3);
String path4=zk.create("/zk-test-ephemeral", "123".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("Success to create znode:"+path4);
}
@Override
public void process(WatchedEvent event)
{
//服务端建立会话连接后,唤醒客户端主线程
System.out.println("Received watched event:"+event);
if (KeeperState.SyncConnected==event.getState())
{
connected.countDown();
}
}
}
可以发现创建的持久化节点和持久化顺序节点在cmd客户端也可以看到,但是创建的临时节点在cmd客户端是看不到的,这是因为临时节点在创建该节点的会话结束后就会内删除。
重新执行以上代码就会抛出NodeExistsException异常,这是因为节点已经存在导致的。
删除已创建的持久化节点,在创建节点结束的代码后增加
TimeUnit.MILLISECONDS.sleep(Integer.MAX_VALUE);
将线程挂起,这样当前会话就不会结束。
再次执行,然后通过cmd客户端执行ls /观察,可以发现创建的临时节点:
2)使用异步方法创建节点