Zookeeper 基本概念和基础操作
Zookeeper概述
什么是zookeeper
- ZooKeeper
- 一个主从架构的分布式框架、开源的对其他的分布式框架的
提供协调服务
(service)
- 一个主从架构的分布式框架、开源的对其他的分布式框架的
- Zookeeper 作为一个分布式的服务框架
- 它提供类似于linux文件系统(有目录节点树)的简版文件系统来存储数据(所有Zookeeper中的文件都一样)
- Zookeeper 维护和监控存储的数据的状态变化,通过监控这些数据状态的变化,从而达到基于数据的集群管理
- 主要用来解决分布式集群中应用系统的一致性问题
为什么要使用zookeeper
-
ZooKeeper简单易用,能够很好的解决分布式框架在运行中,出现的各种协调问题。
- 比如集群master主备切换、节点的上下线感知、统一命名服务、状态同步服务、集群管理、分布式应用配置管理等等(hadoop+zookeeper实现HA)
Zookeeper基本概念
Zookeeper数据结构
Zookeeper主要由以下三个部分实现:
- 简版文件系统(
Znode
)- 基于类似于文件系统的目录节点树方式的数据存储
- 原语
- 可简单理解成ZooKeeper的基本的命令
- 通知机制(
Watcher
-监听器)
Zookeeper数据节点Znode
ZNode分为四类
持久节点 | 临时节点 | |
---|---|---|
非有序节点 | create | create -e |
有序节点 | create -s | create -s -e |
持久节点
-
创建节点/zk_test,并设置数据my_data
create /zk_test my_data
-
持久节点,只有显示的调用命令,才能删除永久节点
delete /zk_test
临时节点
-
client1上创建临时节点
create -e /tmp tmpdata
-
client2上查看client1创建的临时节点
ls /
-
client1断开连接
close
-
client2上观察现象,发现临时节点被自动删除
ls /
有序节点
- 创建有序节点的意义:
- 防止多个不同的客户端在同一目录下,创建
同名
ZNode,由于重名,导致创建失败
- 防止多个不同的客户端在同一目录下,创建
- 有序节点会在节点被创建时,Zookeeper会自动在其节点后追加一个整形数字
- 这个整数是一个由
父节点
维护的自增数字
- 提供了创建
唯一名字
的ZNode的方式
- 这个整数是一个由
如何创建有序节点
- 命令行使用-s选项
Curator编程,可添加一个特殊的属性:CreateMode.EPHEMERAL
Zookeeper基本操作
zkCli命令行
-
启动ZooKeeper集群;在ZooKeeper集群中的每个节点执行此命令
${ZK_HOME}/bin/zkServer.sh start
-
停止ZooKeeper集群(每个节点执行以下命令)
${ZK_HOME}/bin/zkServer.sh stop
-
查看集群状态(每个节点执行此命令)
${ZK_HOME}/bin/zkServer.sh status
-
使用ZooKeeper自带的脚本,连接ZooKeeper的服务器
//nodexx为集群几点名 2181位通信端口,即配置文件${ZK_HOME}/conf/zoo/.cfg的ClientPort zkCli.sh -server node01:2181,node02:2181,node03:2181
客户端会随机
连接server后指定的服务器中的一个,并不会顺序尝试
zkCli常用命令
-
查看ZooKeeper根目录/下的文件列表
ls /
-
创建节点,并指定数据
create /kkb kkb
-
修改节点的数据
set /kkb kkb01
-
删除节点
delete /kkb
其他常用命令
API
创建节点API对比
-原生API
String result = zk.create("/test", "testdata".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
- Curator编程
String zNodeData = "testData";
client.create().
creatingParentsIfNeeded(). //如果父目录不存在,则创建
withMode(CreateMode.PERSISTENT). //创建永久节点
forPath("/testPath/childPath", zNodeData.getBytes());//指定路径及节点数据
原生API
不友好,不具体介绍
curator编程
Curator对ZooKeeper的api做了封装,提供简单易用的API
package cruator;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import java.util.List;
public class ZKCrud {
private static final String ZK_ADDRESS = "node01:2181,node02:2181,node03;2181";
private static final String ZK_PATH = "testPath";
static CuratorFramework client = null;
//初始化,建立连接
public static void init(){
//10次 每次间隔3000毫秒
RetryNTimes retryPolicy = new RetryNTimes(10, 3000);
client = CuratorFrameworkFactory.newClient(ZK_ADDRESS, retryPolicy);
client.start();
System.out.println("zk client started successfully");
}
public static void clean(){
System.out.println("close client");
client.close();
}
//创建永久节点
public static void createPersitsentZNode() throws Exception {
String zNodeData = "testData";
String s = client.create()
.creatingParentContainersIfNeeded() //父节点不存在就创建父节点
.withMode(CreateMode.PERSISTENT) //创建永久节点
.forPath("/testPath/childPath/", zNodeData.getBytes());//指定路径及节点数据
print(s);
}
//创建临时节点
public static void createEphemeralZnode() throws Exception {
String zNodeData = "testData";
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath("/testEphemeraPath/childPath",zNodeData.getBytes());
}
//查询ZNode数据
public static void getZnodeData() throws Exception {
//查新列表
print("ls","/");
List<String> list = client.getChildren().forPath("/");
for (String s :
list) {
System.out.println(s);
}
print(client.getChildren().forPath("/"));
//查询数据
print("get",ZK_PATH);
if (client.checkExists().forPath(ZK_PATH) != null){
print(client.getData().forPath(ZK_PATH));
}else {
print("节点不存在");
}
}
//修改节点数据
public static void setZNode() throws Exception {
// 修改节点数据
String data2 = "hello world";
print("set", ZK_PATH, data2);
if (client.checkExists().forPath(ZK_PATH) != null){
print(client.setData().forPath(ZK_PATH));
print("get", ZK_PATH);
print(client.getData().forPath(ZK_PATH));
}else {
print("节点不存在");
}
}
//删除节点
public static void deleteZNode() throws Exception {
// 删除节点
print("delete", ZK_PATH);
client.delete().forPath(ZK_PATH);
print("ls", "/");
print(client.getChildren().forPath("/"));
}
public static void main(String[] args) throws Exception {
init();
//createEphemeralZnode();
getZnodeData();
//deleteZNode();
clean();
}
private static void print(String... cmds) {
StringBuilder text = new StringBuilder("$ ");
for (String cmd : cmds) {
text.append(cmd).append(" ");
}
System.out.println(text.toString());
}
private static void print(Object result) {
System.out.println(
result instanceof byte[]
? new String((byte[]) result)
: result);
}
}
Curator监听器循环有效期
Watcher
引言:会话
会话的概念
-
客户端要对ZooKeeper集群进行读写操作,得先与某一ZooKeeper服务器建立TCP长连接;此TCP长连接称为建立一个会话
Session
。 -
每个会话有超时时间:
SessionTimeout
- 当客户端与集群建立会话后,如果超过SessionTimeout时间,两者间没有通信,会话超时
会话的特点
- 客户端打开
同一个
Session中的请求以FIFO
(先进先出)的顺序执行;- 如客户端client01与集群建立会话后,先发出一个create请求,再发出一个get请求;
- 那么在执行时,会先执行create,再执行get
- 若打开
两个Session
,无法保证Session间,请求FIFO执行;只能保证一个session中请求的FIFO
会话的生命周期
- 未建立连接
- 正在连接
- 已连接
- 关闭连接
引言:客户端如何获取Zookeeper的最新数据
-
方式一轮询:ZooKeeper以远程服务的方式,被客户端访问;客户端以轮询的方式获得znode数据,效率会比较低(代价比较大)
-
方式二基于通知的机制:
- 客户端在znode上注册一个Watcher监视器
- 当znode上数据出现变化,watcher监测到此变化,通知客户端
什么是Watcher
- 客户端在服务器端,注册事件监听器
- watcher用于监听
znode
数据修改,节点增删等 - 当监听到时间后,watcher会触发通知客户端
- watcher用于监听
ZKCli设置Watcher
注意:Watcher是一个单次触发的操作
Curator
编程中watcher可以设置循环有效
Watcher监听节点变化
监听的节点需要已经存在
#ls path [watch]
#node01 上执行
ls /tmp watch
#node02 上执行
create /tmp /dir01 dir01-data
#观察node-01上变化
[zk: node-01:2181,node-02:2181,node-03:2181(CONNECTED) 87]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/zk_test
Watcher监听znode数据变化
#监控节点数据的变化;
#node02上
get /zk_test watch
#node03上
set /zk_test "junk01"
#观察node2上cli的输出,检测到变化
节点上下线监控
- 原理:
- 节点1(client1)创建临时节点
- 节点2(client2)在临时节点,注册监听器watcher
- 当client1与zk集群断开连接,临时节点会被删除
- watcher发送消息,通知client2,临时节点被删除的事件
-
用到的zk特性:
Watcher+临时节点
-
好处:
通过这种方式,检测和被检测系统不需要直接关联(如client1与client2),而是通过ZK上的某个节点进行关联,大大减少了系统耦合。
-
实现:
client1操作
# 创建临时节点
create -e /temp tmp-data
client2操作
# 在/zk_tmp注册监听器
ls /temp watch
client1操作
# 模拟节点下线
close
观察client2
Curator API设置watcher
public class CuratorWatcher {
/**
* Zookeeper info
*/
private static final String ZK_ADDRESS = "note01:2181,node02:2181,node03:2181";
private static final String ZK_PATH = "/zktest";
public static void main(String[] args) throws Exception {
// 1.Connect to zk
CuratorFramework client = CuratorFrameworkFactory.newClient(
ZK_ADDRESS,
new RetryNTimes(10, 5000)
);
client.start();
System.out.println("zk client start successfully!");
//path cache
///zktest/b/a
PathChildrenCache pathCache = new PathChildrenCache(client, ZK_PATH, true);
//Listener for PathChildrenCache changes
PathChildrenCacheListener listener = new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
switch (event.getType()) {
case CHILD_ADDED: {
System.out.println("Node added: " + ZKPaths.getNodeFromPath(event.getData().getPath()));
break;
}
case CHILD_UPDATED: {
System.out.println("Node changed: " + ZKPaths.getNodeFromPath(event.getData().getPath()));
break;
}
case CHILD_REMOVED: {
System.out.println("Node removed: " + ZKPaths.getNodeFromPath(event.getData().getPath()));
break;
}
default:
break;
}
}
};
//添加监听器
pathCache.getListenable().addListener(listener);
pathCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
System.out.println("Register zk pathCache successfully!");
Thread.sleep(60000);
pathCache.close();
//关闭zk连接
client.close();
}
}
Zookeeper应用场景
- NameNode使用ZooKeeper实现高可用.
- Yarn ResourceManager使用ZooKeeper实现高可用.
- 利用ZooKeeper对HBase集群做高可用配置
- kafka使用ZooKeeper
- 保存消息消费信息比如offset.
- 用于检测崩溃
- 主题topic发现
- 保持主题的生产和消费状态