文章目录
安装
- 下载
wget https://downloads.apache.org/zookeeper/zookeeper-3.6.0/apache-zookeeper-3.6.0-bin.tar.gz
- 解压
tar -zxvf apache-zookeeper-3.6.0-bin.tar.gz
- 配置文件重命名
[root@localhost conf]# mv zoo_sample.cfg zoo.cfg
- 在配置文件中修改dataDir路径
- 创建上面修改的目录
[root@localhost apache-zookeeper-3.6.0-bin]# mkdir zkData
- 启动服务端
[root@localhost bin]# ./zkServer.sh start
- 查看是否启动
jps
- 查看状态
[root@localhost bin]# ./zkServer.sh status
- 启动客户端
[root@localhost bin]# ./zkCli.sh
- 退出客户端
quit
- 停止服务
[root@localhost bin]# ./zkServer.sh stop
配置参数解读
- 通信心跳数,Zookeeper服务器与客户端心跳时间,单位毫秒
Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。(session的最小超时时间是2*tickTime)
tickTime =2000
- LF初始通信时限
集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。
initLimit =10
- LF同步通信时限
集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
syncLimit =5
- 数据文件目录+数据持久化路径,主要用于保存Zookeeper中的数据。
dataDir=/usr/src/apache-zookeeper-3.6.0-bin/zkData
- 客户端连接端口,监听客户端连接的端口。
clientPort =2181
分布式安装部署
集群规划
在192.168.2.2,192.168.2.4,192.168.2.5部署三台Zookeeper服务
配置服务器编号
- 在zkData目录下创建一个myid文件
touch myid
- 编辑myid文件,在其中添加id即可
vim myid
2
配置zoo.cfg
#######################cluster##########################
server.2=192.168.2.2:2888:3888
server.4=192.168.2.4:2888:3888
server.5=192.168.2.5:2888:3888
2888是服务器与集群中的Leader服务器交换信息的端口。
3888是集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
启动服务,查看状态
客户端命令行操作
ZooKeeper -server host:port cmd args
# optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE
addWatch [-m mode] path
addauth scheme auth
close
config [-c] [-w] [-s]
connect host:port
#创建节点
#普通节点:create /sanguo "liubei" create /sanguo/shu "liubei"
#-e:创建短暂节点:create -e /sanguo/wu "zhouyu"
#-s: 创建带序号的节点 create -s /sanguo/wei "cc"
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
# 删除节点
delete [-v version] path
deleteall path [-b batch size]
delquota [-n|-b] path
#获取节点的值
#get /sanguo
# -w 节点的值变化监听,只监听一次变化
get [-s] [-w] path
getAcl [-s] path
getAllChildrenNumber path
getEphemerals path
history
listquota path
# 查看当前znode中所包含的内容
# -s:查看当前节点详细数据
# -w:节点的子节点变化监听(路径变化)
# -R:显示目录
ls [-s] [-w] [-R] path
printwatches on|off
quit
reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
redo cmdno
removewatches path [-c|-d|-a] [-l]
#修改节点的值 set /sanguo/weiguo "simayi"
set [-s] [-v version] path data
setAcl [-s] [-v version] [-R] path acl
setquota -n|-b val path
#查看节点状态
stat [-w] path
sync path
version
内部原理
选举机制
- 半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。
- Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。
- 选举过程:
- 假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动。
- 服务器1启动,此时只有它一台服务器启动了,它发出去的报文没有任何响应,所以它的选举状态一直是LOOKING状态。
- 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1、2还是继续保持LOOKING状态。
- 服务器3启动,根据前面的理论分析,服务器3成为服务器1、2、3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的Leader。
- 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1、2、3、4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了。
- 服务器5启动,同4一样当小弟。
- 假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动。
节点类型
- 持久(Persistent):客户端和服务端断开连接之后,创建的节点不删除
- 持久化目录节点(Persistent)
客户端与Zookeeper断开连接后,该节点依旧存在 - 持久化顺序编号目录节点(Persistent_sequential)
客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号。创建znode时设置顺序标识,znode名称后会有附加一个值,顺序号是一个单调递增计数器,由父节点维护。在分布式系统中,顺序号可以被用于为所有事件进行全局维护,客户端可以通过顺序号推断事件的顺序。
- 持久化目录节点(Persistent)
- 短暂(Ephemeral):客户端和服务端断开连接之后,创建的节点自己删除
- 临时目录节点(Ephemeral)
- 临时顺序编号目录节点(Ephemeral_sequential)
Stat结构体
[zk: localhost:2181(CONNECTED) 21] stat /sanguo
#创建节点的事务zxid
#每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。
#事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid
#如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
cZxid = 0x300000002
# 被创建的毫秒数(从1970年开始)
ctime = Wed Mar 25 09:20:14 CST 2020
#最后更新的事务zxid
mZxid = 0x300000006
#最后修改的毫秒数(从1970年开始)
mtime = Wed Mar 25 09:36:38 CST 2020
#最后更新的子节点zxid
pZxid = 0x300000003
#子节点变化号
cversion = 1
#数据变化号
dataVersion = 2
#访问控制列表的变化号
aclVersion = 0
#如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0
ephemeralOwner = 0x0
#数据长度
dataLength = 8
#子节点数量
numChildren = 1
监听器原理
- 首先要有一个main线程
- 在main线程中创建Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connect),一个负责监听(listener)
- 通过connect将注册的监听事件发送给zookeeper
- 在Zookeeper的注册监听列表中将注册的监听事件添加到列表中
- Zookeeper监听到有数据或者路径变化,就会将这个消息发送给listener线程
- listener线程内部调用了process方法
写数据流程
API应用
环境搭建
- 创建一个Maven工程
- 添加pom文件
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
- log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
创建Zookeeper客户端
private String connectString = "192.168.2.2:2181,192.168.2.4:2181,192.168.2.5:2181";
private int sessionTimeout = 3000000;
private ZooKeeper zkClient;
@Before
public void init() throws IOException {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// System.out.println("---------start----------");
// List<String> children;
// try {
// children = zkClient.getChildren("/", true);
//
// for (String child : children) {
// System.out.println(child);
// }
// System.out.println("---------end----------");
// } catch (KeeperException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
}
});
}
创建节点
@Test
public void createNode() throws KeeperException, InterruptedException {
String path = zkClient.create("/test", "dahaigezuishuai".getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
System.out.println(path);
}
如果出现异常org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCod
,就把sessionTimeout
的值调大一点,直到可以运行为止。
获取子节点 并监控节点的变化
保持该方法处于运行状态
@Test
public void getDataAndWatch() throws KeeperException, InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
在process方法中得到节点变化数据
@Before
public void init() throws IOException {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("---------start----------");
List<String> children;
try {
children = zkClient.getChildren("/", true);
for (String child : children) {
System.out.println(child);
}
System.out.println("---------end----------");
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
判断节点是否存在
@Test
public void exist() throws KeeperException, InterruptedException {
Stat stat = zkClient.exists("/test", false);
System.out.println(stat == null ? "not exist" : "exist");
}
监听服务器节点动态上下线案例
需求
某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知到主节点服务器的上下线。
服务端代码
public class DistributeServer {
private String connectString = "192.168.2.2:2181,192.168.2.4:2181,192.168.2.5:2181";
private int sessionTimeout = 3000000;
private ZooKeeper zkClient;
public static void main(String[] args) throws Exception {
DistributeServer server = new DistributeServer();
// 1 连接zookeeper集群
server.getConnect();
// 2 注册节点
server.regist(args[0]);
// 3 业务逻辑处理
server.business();
}
private void business() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
private void regist(String hostname) throws KeeperException, InterruptedException {
String path = zkClient.create("/servers/server", hostname.getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname +"is online ");
}
private void getConnect() throws IOException {
zkClient = new ZooKeeper(connectString , sessionTimeout , new Watcher() {
@Override
public void process(WatchedEvent event) {
}
});
}
}
客户端代码
public class DistributeClient {
private String connectString = "192.168.2.2:2181,192.168.2.4:2181,192.168.2.5:2181";
private int sessionTimeout = 3000000;
private ZooKeeper zkClient;
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
DistributeClient client = new DistributeClient();
// 1 获取zookeeper集群连接
client.getConnect();
// 2 注册监听
client.getChlidren();
// 3 业务逻辑处理
client.business();
}
private void business() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
private void getChlidren() throws KeeperException, InterruptedException {
List<String> children = zkClient.getChildren("/servers", true);
// 存储服务器节点主机名称集合
ArrayList<String> hosts = new ArrayList<>();
for (String child : children) {
byte[] data = zkClient.getData("/servers/"+child, false, null);
hosts.add(new String(data));
}
// 将所有在线主机名称打印到控制台
System.out.println(hosts);
}
private void getConnect() throws IOException {
zkClient = new ZooKeeper(connectString , sessionTimeout , new Watcher() {
@Override
public void process(WatchedEvent event) {
try {
getChlidren();
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}