ZooKeeper特性与简单使用
本文讲述ZooKeeper的特性及简单使用
ZooKeeper特性与简单使用
概述
搭建了集群后,再认真学习下ZooKeeper。
ZooKeeper的存储结构
存储结构
树形结构,类似Linux系统,第一级是/。ZooKeeper不区分目录(普通目录可以拥有子节点,但是不能存储内容)和文件(普通文件能存储内容,但是不能拥有子节点),都叫节点。
宏观上节点指一台机器,微观上节点指代一个文件或者目录。但是ZooKeeper的节点兼容了目录和文本的特征。
节点设计
既可以拥有子节点,也可以存储内容。
不能存储大量的数据,每个节点存储的数据内容不能超过1M。
ZooKeeper的常用命令
由于是解压安装,缺少很多环境变量配置,每次都先cd到安装目录再执行显然不合适。
新建脚本:start-zk-all.sh
用于启动全部ZK节点:
#!/bin/bash
ZK_HOME=/export/server/zookeeper-3.4.6
for number in {1..3}
do
host=node${number}
echo ${host}
/usr/bin/ssh ${host} "cd ${ZK_HOME};source /etc/profile;${ZK_HOME}/bin/zkServer.sh start"
echo "${host} started"
done
新建脚本:status-zk-all.sh
用于查看全部ZK节点的状态:
#!/bin/bash
ZK_HOME=/export/server/zookeeper-3.4.6
for number in {1..3}
do
host=node${number}
echo ${host}
/usr/bin/ssh ${host} "cd ${ZK_HOME};source /etc/profile;${ZK_HOME}/bin/zkServer.sh status"
done
新建脚本:stop-zk-all.sh
用于关闭全部ZK节点:
#!/bin/bash
ZK_HOME=/export/server/zookeeper-3.4.6
for number in {1..3}
do
host=node${number}
echo ${host}
/usr/bin/ssh ${host} "cd ${ZK_HOME};source /etc/profile;${ZK_HOME}/bin/zkServer.sh stop"
echo "${host} stoped"
done
node1中切换到/bin目录cd /bin
,rz上传3个脚本,使用chmod u+x ./*
给这3个脚本添加可执行权限。
执行后可以看到3个节点的状态。
Client连接Server
切换到ZK的安装目录并查看配置文件:
cd /export/server/zookeeper-3.4.6/conf
cat zoo.cfg
发现Client端口号为2181:
使用cd ..
返回上一级,使用bin/zkCli.sh -server node1:2181,node2:2181,node3:2181
,发现:
Connecting to node1:2181,node2:2181,node3:2181
2021-04-21 20:30:05,692 [myid:] - INFO [main:Environment@100] - Client environment:zookeeper.version=3.4.6-1569965, built on 02/20/2014 09:09 GMT
2021-04-21 20:30:05,696 [myid:] - INFO [main:Environment@100] - Client environment:host.name=node1
2021-04-21 20:30:05,696 [myid:] - INFO [main:Environment@100] - Client environment:java.version=1.8.0_241
2021-04-21 20:30:05,700 [myid:] - INFO [main:Environment@100] - Client environment:java.vendor=Oracle Corporation
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:java.home=/export/server/jdk1.8.0_241/jre
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:java.class.path=/export/server/zookeeper-3.4.6/bin/../build/classes:/export/server/zookeeper-3.4.6/bin/../build/lib/*.jar:/export/server/zookeeper-3.4.6/bin/../lib/slf4j-log4j12-1.6.1.jar:/export/server/zookeeper-3.4.6/bin/../lib/slf4j-api-1.6.1.jar:/export/server/zookeeper-3.4.6/bin/../lib/netty-3.7.0.Final.jar:/export/server/zookeeper-3.4.6/bin/../lib/log4j-1.2.16.jar:/export/server/zookeeper-3.4.6/bin/../lib/jline-0.9.94.jar:/export/server/zookeeper-3.4.6/bin/../zookeeper-3.4.6.jar:/export/server/zookeeper-3.4.6/bin/../src/java/lib/*.jar:/export/server/zookeeper-3.4.6/bin/../conf:.:/export/server/jdk1.8.0_241/lib
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:java.io.tmpdir=/tmp
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:java.compiler=<NA>
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:os.name=Linux
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:os.arch=amd64
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:os.version=3.10.0-1062.el7.x86_64
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:user.name=root
2021-04-21 20:30:05,701 [myid:] - INFO [main:Environment@100] - Client environment:user.home=/root
2021-04-21 20:30:05,702 [myid:] - INFO [main:Environment@100] - Client environment:user.dir=/export/server/zookeeper-3.4.6
2021-04-21 20:30:05,703 [myid:] - INFO [main:ZooKeeper@438] - Initiating client connection, connectString=node1:2181,node2:2181,node3:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@25f38edc
Welcome to ZooKeeper!
2021-04-21 20:30:05,749 [myid:] - INFO [main-SendThread(node3:2181):ClientCnxn$SendThread@975] - Opening socket connection to server node3/192.168.88.11:2181. Will not attempt to authenticate using SASL (unknown error)
JLine support is enabled
2021-04-21 20:30:05,844 [myid:] - INFO [main-SendThread(node3:2181):ClientCnxn$SendThread@852] - Socket connection established to node3/192.168.88.11:2181, initiating session
[zk: node1:2181,node2:2181,node3:2181(CONNECTING) 0] 2021-04-21 20:30:05,900 [myid:] - INFO [main-SendThread(node3:2181):ClientCnxn$SendThread@1235] - Session establishment complete on server node3/192.168.88.11:2181, sessionid = 0x378eeb1e5800000, negotiated timeout = 30000
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
使用node1作Client连接Server,node1连接到了自己!!!node1还连接到了node2和node3。
使用bin/zkCli.sh -server node1:2181
连接node1.或者使用bin/zkCli.sh -server node2:2181
或者使用bin/zkCli.sh -server node3:2181
只连一台机器也没有任何问题。这是因为ZK是公平节点,每个节点存储的内容是一致的(数量上至少有多一半的机器存储着相同的内容),故只需要一台就可以使用。但是如果只给定一台,则Client只能连接到这台,Server宕机后将无法连接到另外的节点。规范的操作应该是给定多台的地址,当连接一台失败时,自动连接到其它的机器。
ZooKeeper的基本命令
连接后,命令行的状态为:[zk: node1:2181,node2:2181,node3:2181(CONNECTED) 0]
,输入help
即可看到帮助:
ZooKeeper -server host:port cmd args
stat path [watch]
set path data [version]
ls path [watch]
delquota [-n|-b] path
ls2 path [watch]
setAcl path acl
setquota -n|-b val path
history
redo cmdno
printwatches on|off
delete path [version]
sync path
listquota path
rmr path
get path [watch]
create [-s] [-e] path data acl
addauth scheme auth
quit
getAcl path
close
connect host:port
给出了命令格式及参数。要注意:ZK的节点不能使用相对路径,需要使用绝对路径。
列举
标准格式:ls path [watch]
使用ls /
可以查看目录下的内容,使用ls /zookeeper
还可以看到下一级的内容:
创建
标准格式:create [-s] [-e] path data
读取
标准格式:get path [watch]
可以看到cZxid是递增的。
修改
标准格式:set path data [version]
查看
标准格式:get path
可以看出内容已经发生了改变,dataversion也+1。
删除
标准格式:rmr path
可以看出成功删除了节点文件。
退出
这一步就比较随意了,使用quit
或者ctrl+c
都可以。
ZooKeeper的特性
节点类型
永久节点
默认创建的节点都是永久节点。create path data
这条命令创建的普通的节点(也就是永久节点)如果不手动删除,将会永远存在。上方也看到了不允许创建同名的节点。
永久有序节点
命令create -s path data
允许创建同名的节点,会自动编号。
临时节点-e
命令create -e path data
创建的临时节点会在Client断开时自动删除。
临时节点的生命周期随着Client产生,Client断开时,创建的临时节点会自动删除。
临时有序节点
命令create -e -s path data
创建的节点包含了临时节点和有序节点的特性,既做编号也时临时节点。
序列节点-s
可以创建同名的节点,ZK会自动对同名节点进行编号。
监听机制
功能
监听:设置监听某个节点,如果这个节点发生变化,可以立即受到这个变化的通知。
实现
ls path [watch]
get path [watch]
当使用watch时就会开启监听,发生变化时自动提示,貌似只能生效一次。。。
ZooKeeper的选举
所有辅助选举、元数据存储都在分布式工具中封装好了,不用我们干预。
辅助Active Master选举
临时节点的作用:辅助选举。
监听机制的作用:辅助切换。
使用临时节点
这是最常见的方式。
MasterA和MasterB,都启动后,让它们同时创建同一个临时节点file,普通的临时节点不允许重复。假设A创建成功,B就无法创建成功(反之亦然),成功的Master就是Active的Master(Leader),失败的Master就是Standby的Master(Follower)。
假设Active的Master节点A故障,临时节点会被自动删除。让B监听,如果临时节点小时被B监听到,B就会立即创建这个临时节点并成为新的Active的Master。A状态正常后已经无法创建该同名节点(说明已经有Active的Master节点),自己只能是Standby并且监听。
使用临时有序节点
MasterA和MasterB,都启动后,让它们同时创建同一个临时有序节点file,临时有序节点允许重复但是会标记编号。假设A先创建成功,A的编号最小,就会作为Active的Master,B就只能是Standby的Master。
假设Active的Master节点A故障,临时有序节点会被自动删除。让B监听,如果临时有序节点有1个消失,B就会监听到,成为新的Active的Master。A重启以后发现已经存在一个临时节点,说明已经有一个Active,无论A是否创建新的临时有序节点都不可能比当前B的临时有序节点的编号小,故A只能是Standby状态并且设置监听。
内部Leader节点选举
内部Leader节点的选举规则
zxid:数据id,用于标记数据的更新状态,zxid越大,代表数据越新。
myid:权重id(节点标识id),每台ZooKeeper都有一个唯一标识的id(搭建集群时已经给定),选举时也会作为权重的id。
规则:先比较zxid(zxid大的就是leader),如果zxid相同,再去比较myid,谁大(且超过半数)谁就是leader。
内部Leader节点的选举过程
集群第一次启动
由于搭集群时已经设定了node1的myid=1,node2的myid=2,node3的myid=3,集群刚启动时zxid都=0,只能比较myid。
由于是使用node1的命令行通过免密匙登录和shell脚本启动node2和node3的ZooKeeper服务,node1一定是最先启动,先给自己投一票。按照shell脚本的执行顺序,正常情况应该是node2接着启动,也给自己投了一票,此时node1与node2都是1票。由于node2的myid比node1大,node1转而把自己的票投给node2,此时node2拥有2票。此时已有超过半数的机器投了node2的票,node2成为Leader。
node3启动后,还是会发现已经有超过一半的机器投了node2的票,就算给自己投票,票数也不可能比node2更多,于是node3和node1都是Follower。
这就解释了喂猫平时这种启动方式大概率node2是leader。
但是也会有意外!!!
比如node2网络延时比node3高,或者硬盘读取等因素,导致node2启动的比node3慢,leader就是node3。
集群故障恢复启动
假设node2为leader,node1和node3为follower,写入文件时node2的zxid=5,假设node1先同步写入完成(node1的zxid=5),此时超过一半的节点已经完成写入,如果此时leader node2宕机,作为follower的node3的zxid还是4(没有来得及同步写入并使zxid=5),此时就会重新选举,先比较zxid,更大的node1直接就会成为新的leader(数据越新越能保证数据的安全性),并且将node3的数据同步为和自己一致,这样便保证了数据的安全性和一致性。
ZK节点数一般为奇数
虽然节点的个数可以是偶数,但是奇数台和偶数台的容错率是相同的。例如:5台和6台,都是只容许小于一半的机器宕机,5台允许宕机2台(假设宕机了3台且都是已经更新数据的机器,剩下的2台都是未更新数据的机器,如果放任那2台变成leader并继续工作会出现数据回滚),6台也是允许宕机2台(假设6台更新数据的有4台,宕机的3台恰好都是已经更新过数据的[这种做法是为了提高响应速度],只剩3台未更新数据的服务器,此时可能发生脑裂,集群的可靠性得不到保障,破坏了选举机制),超过容错率就没办法保证数据的一致性、安全性、可靠性。显然奇数台和多一台的偶数台相比区别并不大,偶数台还会浪费一台机器的资源,故一般情况都是用奇数台。
ZooKeeper的Java API
环境搭建
新建Maven项目,新建个Module,在pom.xml添加依赖项:
<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.11</version>
</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>
新建Java类public class ZooKeeperClientDemo {}
,为了方便就不使用psvm构造main主方法入口了,直接配置:
创建客户端连接对象:
CuratorFramework client = null;
构建连接:
@Before
public void getConnection() {
//构建重试连接
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(5000, 3);
//构建连接客户端
client = CuratorFrameworkFactory.newClient("node1:2181", retry);
}
如果这一步漏了连接IP与端口会报错:
java.net.ConnectException: Connection refused: no further information
释放资源:
@After
public void closeConnect() {
client.close();
}
增删改查
创建节点:
@Test
public void createNode() throws Exception {
//启动
client.start();
//创建节点
//创建永久节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/bigdata01", "spark".getBytes());
//创建临时节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/bigdata01", "spark".getBytes());
Thread.sleep(10000);//暂停10s
}
查询节点:
@Test
public void getNode() throws Exception {
//启动
client.start();
//查询
byte[] bytes = client.getData().forPath("/bigdata01");
System.out.println("/bigdata01的内容是"+new String(bytes));
}
修改节点:
@Test
public void setNode() throws Exception {
//启动
client.start();
//修改节点
client.setData().forPath("/bigdata","digital monster".getBytes());
}
删除节点:
@Test
public void rmNode() throws Exception {
//启动
client.start();
//删除
client.delete().forPath("/bigdata01");
}
执行:
可以看到创建永久节点成功。
过了10s后临时节点消失。
也可以查询。
也可以删除。
以后就可以使用JavaAPI实现对ZooKeeper节点的增删改查了!!!类似JDBC修改MySQL数据库的骚操作,还是很方便的。
更多推荐
所有评论(0)