Zookeeper
1.Zookeeper概述
- Zookeeper是一个开源的分布式协调服务框架,主要用来解决分布式集群中应用系统的一致性问题和数据管理问题
2.Zookeeper特点
- Zookeeper本质上是一个分布式文件系统,也可以理解为一个数据库
- Zookeeper中存储的其实是一个又一个Znode,Znode是Zookeeper中的节点
- Znode是有路径的,例如/data/host1,这个路径也可以理解为是Znode中的Name
- Znode也可以携带数据,例如某个Znode的路径是/data/host1,其值是一个字符串“192.168.0.1”
- 正因为Znode的特性,所以Zookeeper可以对外提供一个类似于文件系统的视图,可以通过操作文件系统的方式操作Zookeeper
- 使用路径获取Znode
- 获取Znode携带的数据
- 修改Znode携带的数据
- 删除Znode
- 添加Znode
3.Zookeeper架构
Zookeeper集群是一个基于主从架构的高可用集群
角色 | 描述 |
---|---|
领导者(Leader) | 集群工作的核心(每个集群同一时间只有一个);集群内部各个服务器的调度者;负责进行投票选举;处理事务性(写操作)请求;参与集群投票 |
跟随者(Follower) | 接收客户端请求,并向客户端返回结果;处理非事务性(读操作)请求;转发事务给Leader;参与集群投票 |
观察者(Observer) | 接收客户端请求,并向客户端返回结果;处理非事务性(读操作)请求;转发事务给Leader;不参与集群投票 |
客户端(Client) | 请求发起方 |
4.Zookeeper应用场景
- 数据发布/订阅
- 命名服务
- 分布式协调/通知
- 心跳检测
- 工作进度汇报
- 系统调度
- 分布式锁
- 分布式队列
5.Zookeeper的选举机制
Leader选举是保证分布式数据一致性的关键所在。当Zookeeper集群中的一台服务器出现以下两种情况之一时,需要进入Leader选举
-
服务器启动时期
① 每个Server发出一个投票,由于是初始情况,Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举服务器的myid和ZXID,使用(myid,ZXID)表示,此时Server1的投票为(1,0),Server2的投票为(2,0),然后各自将这个投票发给集群中的其他机器。
② 接受来自各个服务器的投票。集群中每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
③ 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下:
- 优先检查ZXID。ZXID比较大的服务器优先作为Leader
- 如果ZXID相同,那么就比较myid,myid较大的服务器作为Leader。
myid大的,更新自己的投票。如Server1更新自己的投票为(2,0),Server2不动。
④ 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器收到相同的投票信息,对于Server1和Server2而言,都统计出集群中已经有两台机器接收到(2,0)的投票信息,此时便认为已经选出了Leader
⑤ 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态(FOLLOWING和LEADING)
-
服务器运行时期的Leader选举
正常运行期间新加入的机器或非Leader机器并不会对Leader产生影响。若Leader服务器挂掉,则集群暂停对外服务,进行新一轮选举,过程与启动时期类似。
6.Zookeeper安装
集群规划
服务器IP | 主机名 | myid |
---|---|---|
192.168.217.100 | node01 | 1 |
192.168.217.110 | node02 | 2 |
192.168.217.120 | node03 | 3 |
第一步:下载Zookeeper压缩包
http://archive.apache.org/dist/zookeeper/
第二步:上传并解压
[root@node01 zookeeper-3.4.9]# tar zvxf ./zookeeper-3.4.9.tar.gz -C /export/servers/
第三步:修改配置文件
第一台机器修改配置文件
[root@node01 conf]# cd /export/servers/zookeeper-3.4.9/conf/
[root@node01 conf]# cp zoo_sample.cfg zoo.cfg
[root@node01 conf]# mkdir -p /export/servers/zookeeper-3.4.9/zkdatas/
[root@node01 conf]# vim zoo.cfg
修改以下内容
dataDir=/export/servers/zookeeper-3.4.9/zkdatas
# 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
#集群中的服务器地址
server.1=node01:2888:3888
server.2=node02:2888:3888
server.3=node03:2888:3888
第四步:添加myid配置
/export/servers/zookeeper-3.4.9/zkdatas/ 下创建一个文件,文件名为myid,文件内容为1
[root@node01 conf]# echo 1 > /export/servers/zookeeper-3.4.9/zkdatas/myid
第五步:将zookeeper-3.4.9远程拷贝到node02和node03
[root@node01 zkdatas]# scp -r /export/servers/zookeeper-3.4.9/ node02:/export/servers/
[root@node01 zkdatas]# scp -r /export/servers/zookeeper-3.4.9/ node03:/export/servers/
再将node02和node03中的myid内容分别修改为2和3即可。
第六步:启动Zookeeper
在三台机器上分别执行bin/zkServer.sh start
[root@node01 zookeeper-3.4.9]# bin/zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /export/servers/zookeeper-3.4.9/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[root@node01 zookeeper-3.4.9]# jps
7761 QuorumPeerMain
7789 Jps
使用jps命令出现QuorumPeerMain进程即启动成功
查看状态
[root@node01 zookeeper-3.4.9]# bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /export/servers/zookeeper-3.4.9/bin/../conf/zoo.cfg
Mode: follower
[root@node02 zookeeper-3.4.9]# bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /export/servers/zookeeper-3.4.9/bin/../conf/zoo.cfg
Mode: leader
[root@node03 zookeeper-3.4.9]# bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /export/servers/zookeeper-3.4.9/bin/../conf/zoo.cfg
Mode: follower
7.Zookeeper数据模型
-
Zookeeper的数据模型,在结构上和标准文件的系统非常相似,拥有一个层次的命名空间,都是采用树形层次结构。
-
Zookeeper树中每个节点被称为一个Znode,和文件系统的目录树一样,Zookeeper树中的每个节点可以拥有子节点。
不同之处:
- Znode兼具文件和目录两种特点。
- Znode存储文件大小有限制。Zookeeper用来管理和调度数据,比如分布式应用中的配置文件信息、状态信息、汇集信息等等。一个Znode一般不超过1M
- Znode通过路径引用。路径必须是绝对的、唯一的
- 每个Znode由三部分组成:
- stat:状态信息,描述Znode的版本,权限等信息
- data:与Znode关联的数据
- children:该Znode下的子节点
8.Znode节点类型
Znode节点有两种,分别为临时节点和永久节点。节点的类型在创建时被确定,并且不能改变。
- 临时节点:该节点的生命周期依赖于创建他们的会话。一旦会话结束,临时节点将被自动删除,当然也可以手动删除。临时节点不允许有子节点。
- 永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
Znode还有一个序列化的特性。如果创建时指定的话,该Znode后会自动追加一个序列号,类似一个编号。该序列号不断增长,即先创建的大于后创建的。序列号对于此父节点是唯一的,这样便于记录每个节点创建的先后顺序。格式为“%10d”,例如0000000001.
这样便存在四种类型节点:
- PERSISTENT
- EPHEMRAL
- PERSISTENT_SEQUENTIAL
- EPHEMERAL_SEQUENTIAL
9.Zookeeper的Shell客户端操作
-
登录Zookeeper客户端
[root@node01 zookeeper-3.4.9]# bin/zkCli.sh -server node01:2181 #端口号在zoo.cnf中指定 或 [root@node01 zookeeper-3.4.9]# bin/zkCli.sh #登录本地的Zookeeper
命令 说明 参数 create [-s] [-e] path data acl 创建Znode -s指定顺序节点,-e指定临时节点 ls path [watch] 列出path下所有子Znode get path [watch] 获取path对应Znode的数据和属性 ls2 path [watch] 查看path下所有子Znode以及子Znode的属性 set path data [version] 更新节点数据 version数据版本 delete path [version] 删除节点,如果有子Znode无法删除 version数据版本 rmr path 递归删除节点 setquota -n | -b val path 修改Znode配额 -n设置子节点最大个数,-b设置节点数据 -
操作实例
列出“/”下所有Znode
[zk: node01:2181(CONNECTED) 0] ls / [zookeeper]
创建永久节点hello,数据为world
[zk: node01:2181(CONNECTED) 1] create /hello world
创建临时节点
[zk: node01:2181(CONNECTED) 8] create -e /tmp world
创建永久序列化节点
[zk: node01:2181(CONNECTED) 9] create -s /zhangsan boy Created /zhangsan0000000003
创建临时序列化节点
[zk: node01:2181(CONNECTED) 10] create -s -e /lisi girl Created /lisi0000000004
修改、获取节点数据
[zk: node01:2181(CONNECTED) 1] set /hello hello [zk: node01:2181(CONNECTED) 2] get /hello
删除节点
[zk: node01:2181(CONNECTED) 3] delete /zhangsan0000000003 #无子节点,可以删除 [zk: node01:2181(CONNECTED) 14] create /hello/aaa wangbin [zk: node01:2181(CONNECTED) 17] delete /hello #有子节点,无法删除 Node not empty: /hello [zk: node01:2181(CONNECTED) 18] rmr /hello #递归删除
查看历史
[zk: node01:2181(CONNECTED) 19] history
-
节点属性
每个Znode都包含了一系列的属性,通过get获取
dataVersion:数据版本号,每次set操作dataVersion加1(即使数据没变),可有效避免数据更新时出现先后顺序问题。
cversion:子节点版本号
aclVersion:ACL版本号
cZxid:Znode创建的事务ID
mZxid:Znode被修改的事务ID,即每次对Znode的修改都会更新
ctime:节点创建时的时间戳
mtime:节点最新一次更新时的时间戳
ephemeralOwner:如果该节点为临时节点,ephemeralOwner表示与该节点绑定的session id,如果不是则为0。
-
watch机制
- 类似于数据库中的触发器,对某个Znode设置Watcher,当Znode发生变化的时候,WatchManager会调用对应的Watcher
- 当Znode发生删除,修改,创建,子节点修改的时候,对应的Watcher会得到通知
- Watcher的特点
- 一次性触发。一个Watcher只能被触发一次,如果需要继续监听,则需要再次添加Watcher
- 事件封装。Watcher得到的事件是被封装过的,包括三个内容:keeperState,eventType,path
keeperState eventType 触发条件 说明 None 连接成功 SyncConnected NodeCreated Znode被创建 此时处于连接状态 SyncConnected NodeDeleted Znode被删除 此时处于连接状态 SyncConnected NodeDataChanged Znode数据被改变 此时处于连接状态 SyncConnected NodeChildChanged Znode的子Znode数据被改变 此时处于连接状态 Disconnected None 客户端与服务器断开连接 此时服务器与客户端端口连接状态 Expired None 会话超时 会收到一个SessionExpiredException AuthFailed None 权限验证失败 会收到一个AuthFailedException
10.Zookeeper的JavaAPI操作
这里操作Zookeeper的JavaAPI使用的是一套Zookeeper客户端框架Curator,解决了很多Zookeeper客户端非常底层的细节开发工作。
Curator包含几个包:
- curator-framework:对Zookeeper的底层api的一些封装
- curator-recipes:封装了一些高级特性,如Cache事件监听、选举、分布式锁、分布式计算器等
Maven依赖(Curator版本:2.12.0,对应Zookeeper的版本为3.4.x,跨版本可能会有兼容性问题)
创建Maven工程,导入jar包
<dependencies>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
创建永久节点
package com.wangbin.zookeeper_api;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.junit.Test;
public class ZooKeeperAPITest {
@Test
public void createZnode() throws Exception {
//1.定制重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,1); //重试的时间间隔为1000ms,重试次数为1
//2.获取客户端对象
String connectionStr = "192.168.217.100:2181,192.168.217.110:2181,192.168.217.120:2181";
/*
param1:要连接的Zookeeper服务器列表
param2:会话的超时时间
param3:链接超时时间
param4:重试策略
*/
CuratorFramework client = CuratorFrameworkFactory.newClient(connectionStr,8000,8000,retryPolicy);
//3.开启客户端
client.start();
//4.创建节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/hello","world".getBytes());
//5.关闭客户端
client.close();
}
}
创建临时节点
//4.创建节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/tmp","world".getBytes());
Thread.sleep(5000);
进程结束会话结束,临时节点消失。
修改节点数据
//4.修改节点数据
client.setData().forPath("/hello","aaa".getBytes());
获取节点数据
//4.获取节点数据
byte[] data = client.getData().forPath("/hello");
System.out.println(new String(data));
节点watch机制
@Test
public void watchZnode() throws Exception {
//1.定制重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 1);
//2.获取客户端
String connectionStr = "192.168.217.100:2181,192.168.217.110:2181,192.168.217.120:2181";
CuratorFramework client = CuratorFrameworkFactory.newClient(connectionStr,8000,8000,retryPolicy);
//3.启动客户端
client.start();
//4.创建一个TreeCache对象,指定要监控的节点路径
TreeCache treeCache = new TreeCache(client,"/hello");
//5.自定义一个监听器
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
ChildData data = treeCacheEvent.getData();
if(data!=null){
switch (treeCacheEvent.getType()){
case NODE_ADDED:
System.out.println("新增节点!");
break;
case NODE_REMOVED:
System.out.println("移除节点!");
break;
case NODE_UPDATED:
System.out.println("更新节点!");
break;
default:
break;
}
}
}
});
//6.开始监听
treeCache.start();
Thread.sleep(100000);
client.close();
}