ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。ZooKeeper包含一个简单的原语集,提供Java和C的接口。
ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在zookeeper-3.4.8\src\recipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。
分布式应用的优点
分布式应用的挑战
Apache ZooKeeper是由集群(节点组)使用的一种服务,用于在自身之间协调,并通过稳健的同步技术维护共享数据。ZooKeeper本身是一个分布式应用程序,为写入分布式应用程序提供服务。
ZooKeeper提供的常见服务如下 :
一旦ZooKeeper集合启动,它将等待客户端连接。客户端将连接到ZooKeeper集合中的一个节点。它可以是leader或follower节点。一旦客户端被连接,节点将向特定客户端分配会话ID并向该客户端发送确认。如果客户端没有收到确认,它将尝试连接ZooKeeper集合中的另一个节点。 一旦连接到节点,客户端将以有规律的间隔向节点发送心跳,以确保连接不会丢失。
让我们分析在ZooKeeper集合中拥有不同数量的节点的效果。
如果我们有四个节点而两个节点故障,它将再次故障。类似于有三个节点,额外节点不用于任何目的,因此,最好添加奇数的节点,例如3,5,7。
组件 | 描述 |
---|---|
写入(write) | 写入过程由leader节点处理。leader将写入请求转发到所有znode,并等待znode的回复。如果一半的znode回复,则写入过程完成。 |
读取(read) | 读取由特定连接的znode在内部执行,因此不需要与集群进行交互。 |
复制数据库(replicated database) | 它用于在zookeeper中存储数据。每个znode都有自己的数据库,每个znode在一致性的帮助下每次都有相同的数据。 |
Leader | Leader是负责处理写入请求的Znode。 |
Follower | follower从客户端接收写入请求,并将它们转发到leader znode。 |
请求处理器(request processor) | 只存在于leader节点。它管理来自follower节点的写入请求。 |
原子广播(atomic broadcasts) | 负责广播从leader节点到follower节点的变化。 |
I. 修改文件名zoo_sample.cfg为zoo.cfg.
II. 修改zoo.cfg文件内容
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=D:\\zookeeper-3.4.10.tar\\zookeeper-3.4.10\\data
dataLogDir=D:\\zookeeper-3.4.10.tar\\zookeeper-3.4.10\\log
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
III. 配置文件解析
zkServer.cmd
jps -l -v
zkCli.cmd 127.0.0.1:2181
与ZooKeeper集合进行交互的应用程序称为 ZooKeeper客户端或简称客户端。
Znode是ZooKeeper集合的核心组件,ZooKeeper API提供了一小组方法使用ZooKeeper集合来操纵znode的所有细节。
客户端应该遵循以步骤,与ZooKeeper集合进行清晰和干净的交互。
ZooKeeper API的核心部分是ZooKeeper类。它提供了在其构造函数中连接ZooKeeper集合的选项,并具有以下方法:
新建Java工程,并导入/lib/log4j-1.2.16.jar、/lib/slf4j-api-1.6.1.jar、/lib/slf4j-log4j12-1.6.1.jar、zookeeper-3.4.10.jar,将/conf/路径下的log4j.properties文件拷贝到项目的src目录下
I. 方法原型
ZooKeeper(String connectionString, int sessionTimeout, Watcher watcher)
/**
* ZooKeeper连接管理
* @author mazaiting
*/
public class ZooKeeperConnection {
/**
* 定义ZooKeeper实例
*/
private ZooKeeper zooKeeper;
/**
* 用户停止(等待)主线程,直到客户端与ZooKeeper集合连接
*/
private final CountDownLatch connectedSignal = new CountDownLatch(1);
/**
* 连接
* @param host 主机地址
* @return ZooKeeper对象
* @throws IOException IO异常
* @throws InterruptedException 中断异常
*/
public ZooKeeper connect(String host) throws IOException, InterruptedException {
// 创建ZooKeeper对象
zooKeeper = new ZooKeeper(host, 5000, new Watcher(){
/**
*
*/
@Override
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected) {
connectedSignal.countDown();
}
}
});
// 等待
connectedSignal.await();
return zooKeeper;
}
/**
* 关机ZooKeeper的连接
* @throws InterruptedException 中断异常
*/
public void close() throws InterruptedException {
zooKeeper.close();
}
}
I. 方法原型
create(String path, byte[] data, List acl, CreateMode createMode)
II. 代码
/**
* 创建节点
* @author mazaiting
*/
public class ZKCreate {
/**
* 创建静态的ZooKeeper实例
*/
private static ZooKeeper zooKeeper;
/**
* 创建静态的连接实例
*/
private static ZooKeeperConnection conn;
/**
* 创建节点
* @param path Znode路径
* @param data 存储在Znode路径中的数据
* @throws KeeperException ZooKeeper异常
* @throws InterruptedException 中断异常
*/
public static void create(String path, byte[] data) throws KeeperException, InterruptedException {
zooKeeper.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
public static void main(String[] args) {
// znode路径
String path = "/MyFirst";
// 二进制数据
byte[] data = "My First ZooKeeper app".getBytes();
try {
// 创建连接对象
conn = new ZooKeeperConnection();
// 连接, 并返回ZooKeeper
zooKeeper = conn.connect("127.0.0.1");
// 创建Znode
create(path, data);
// 关闭
conn.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
III. 测试
执行结果:
zkCli.cmd
开启一个客户端窗口,get /MyFirst
(get 路径名)查看数据I. 方法原型
exists(String path, boolean watcher)
/**
* 判断某个路径是否存在
* @author mazaiting
*/
public class ZKExists {
/**
* 定义静态的ZooKeeper对象
*/
private static ZooKeeper zooKeeper;
/**
* 定义静态的ZooKeeper连接对象
*/
private static ZooKeeperConnection conn;
/**
* 判断某个路径是否存在
* @param path 路径
* @return 返回状态
* @throws KeeperException ZooKeeper异常
* @throws InterruptedException 中断异常
*/
public static Stat exists(String path) throws KeeperException, InterruptedException {
return zooKeeper.exists(path, true);
}
public static void main(String[] args) {
// znode路径
String path = "/MyFirst";
try {
// 创建连接对象
conn = new ZooKeeperConnection();
// 连接, 并返回ZooKeeper
zooKeeper = conn.connect("127.0.0.1");
// 检测路径是否存在
Stat stat = exists(path);
// 判断是否为空
if (null != stat) {
// 存在打印其版本信息
System.out.println("Node exists and the node version is " + stat.getVersion());
} else {
System.out.println("Node does not exists");
}
// 关闭
conn.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
III. 测试
I. 方法原型
getData(String path, Watcher watcher, Stat stat)
II. 代码
/**
* 获取数据
* @author mazaiting
*/
public class ZKGetData {
/**
* 定义静态的ZooKeeper对象
*/
private static ZooKeeper zooKeeper;
/**
* 定义静态的ZooKeeper连接对象
*/
private static ZooKeeperConnection conn;
/**
* 判断某个路径是否存在
* @param path 路径
* @return 返回状态
* @throws KeeperException ZooKeeper异常
* @throws InterruptedException 中断异常
*/
public static Stat exists(String path) throws KeeperException, InterruptedException {
return zooKeeper.exists(path, true);
}
public static void main(String[] args) {
// 路径
String path = "/MyFirst";
// 用户停止(等待)主线程,直到客户端知道获取到数据
final CountDownLatch connectedSignal = new CountDownLatch(1);
try {
// 创建连接对象
conn = new ZooKeeperConnection();
// 连接
zooKeeper = conn.connect("127.0.0.1");
// 检测是否存此路径
Stat stat = exists(path);
// 判断是否存在
if (null != stat) {
// 获取字节数组
byte[] bytes = zooKeeper.getData(path, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 检测状态
if (Event.EventType.None == event.getType()) {
// 判断状态
switch (event.getState()) {
case Expired:
// 关闭
connectedSignal.countDown();
break;
default:
break;
}
} else {
try {
byte[] bn = zooKeeper.getData(path, false, null);
// 将字节数组转换为字符创
String data = new String(bn, "UTF-8");
// 打印字符串
System.out.println(data);
// 关闭
connectedSignal.countDown();
} catch (Exception e) {
// 打印错误日志
System.out.println(e.getMessage());
}
}
}
}, null);
// 将字节数组转换为字符创
String data = new String(bytes, "UTF-8");
// 打印字符串
System.out.println(data);
// 等待
connectedSignal.await();
} else {
// 节点不存在
System.out.println("Node does not exists");
}
} catch (Exception e) {
// 打印错误日志
System.out.println(e.getMessage());
}
}
}
III. 测试
I. 方法原型
setData(String path, byte[] data, int version)
/**
* 设置数据
* @author mazaiting
*/
public class ZKSetData {
/**
* 定义静态的ZooKeeper对象
*/
private static ZooKeeper zooKeeper;
/**
* 定义静态的ZooKeeper连接对象
*/
private static ZooKeeperConnection conn;
/**
* 更新数据
* @param path 路径
* @param data 二进制数据
* @throws KeeperException ZooKeeper异常
* @throws InterruptedException 中断异常
*/
public static void update(String path, byte[] data) throws KeeperException, InterruptedException {
zooKeeper.setData(path, data, zooKeeper.exists(path, true).getVersion());
}
public static void main(String[] args) {
// 路径
String path = "/MyFirst";
// 二进制数据
byte[] data = "Success".getBytes();
try {
// 创建连接对象
conn = new ZooKeeperConnection();
// 连接并获取ZooKeeper
zooKeeper = conn.connect("127.0.0.1");
// 更新数据
update(path, data);
} catch (Exception e) {
// 打印错误信息
System.out.println(e.getMessage());
}
}
}
III. 测试
运行程序后,在命令提示符中使用zkCli.cmd
开启一个客户端窗口,连接成功后, 输入get /MyFirst(get 路径名)查看数据
I. 方法原型
getChildren(String path, Watcher watcher)
/**
* 获取子节点
* @author mazaiting
*/
public class ZKGetChildren {
/**
* 定义静态的ZooKeeper对象
*/
private static ZooKeeper zooKeeper;
/**
* 定义静态的ZooKeeper连接对象
*/
private static ZooKeeperConnection conn;
/**
* 判断某个路径是否存在
* @param path 路径
* @return 返回状态
* @throws KeeperException ZooKeeper异常
* @throws InterruptedException 中断异常
*/
public static Stat exists(String path) throws KeeperException, InterruptedException {
return zooKeeper.exists(path, true);
}
public static void main(String[] args) {
// 路径
String path = "/MyFirst";
try {
// 创建连接对象
conn = new ZooKeeperConnection();
// 连接,并获取ZooKeeper对象
zooKeeper = conn.connect("127.0.0.1");
// 判断路径是否存在
Stat stat = exists(path);
// 判断是否为空
if (null != stat) {
// 获取孩子列表
List children = zooKeeper.getChildren(path, false);
// 遍历孩子列表
for(int i = 0; i < children.size(); i++) {
// 打印数据
System.out.println(children.get(i));
}
} else {
// 路径不存在
System.out.println("Node does not exists");
}
} catch (Exception e) {
// 打印错误信息
System.out.println(e.getMessage());
}
}
}
III. 测试
先在命令提示符窗口中添加两个孩子节点
运行项目
I. 方法原型
delete(String path, int version)
/**
* 删除路径
* @author mazaiting
*/
public class ZKDelete {
/**
* 定义静态的ZooKeeper对象
*/
private static ZooKeeper zooKeeper;
/**
* 定义静态的ZooKeeper连接对象
*/
private static ZooKeeperConnection conn;
/**
* 删除节点
* @param path 路径
* @throws InterruptedException 中断异常
* @throws KeeperException ZooKeeper异常
*/
public static void delete(String path) throws InterruptedException, KeeperException {
zooKeeper.delete(path, zooKeeper.exists(path, true).getVersion());
}
public static void main(String[] args) {
// 路径
String path = "/MyFirst/first";
try {
// 创建ZooKeeper连接
conn = new ZooKeeperConnection();
// 连接
zooKeeper = conn.connect("127.0.0.1");
// 删除
delete(path);
} catch (Exception e) {
// 打印错误信息
System.out.println(e.getMessage());
}
}
}
III. 测试
删除前/MyFirst的numChildren量比删除后多1