文章目录
- 1、选举机制(面试重点)
- 1.1、Zookeeper选举机制——第一次启动
- 1.2、Zookeeper选举机制——非第一次启动
- 2、ZK 集群启动停止脚本
- 3、客户端命令行操作
- 3.1、命令行语法
- 3.2、znode 节点数据信息
- 3.3、节点类型(持久/短暂/有序号/无序号)
- 3.4、监听器原理
- 3.4.1、监听器原理
- 3.5、节点删除与查看
- 4、客户端 API 操作
- 4.1、IDEA 环境搭建
- 4.2、创建 ZooKeeper 客户端
- 4.3、创建子节点
- 4.4、获取子节点并监听节点变化
- 4.5、判断 Znode 是否存在
- 5、客户端向服务端写数据流程
- 5.1、写流程之写入请求直接发送给Leader节点
- 5.2、写流程之写入请求发送给follower节点
1、选举机制(面试重点)
1.1、Zookeeper选举机制——第一次启动
- 服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票) ,选举无法成,服务器1状态保持为LOOKING;
- 服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的myid比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING;
- 服务器3启动, 发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;
- 服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING;
- 服务器5启动, 同4一样当小弟。
1.2、Zookeeper选举机制——非第一次启动
- 当ZooKeeper集群中的一台服务器出现以下两种情况之一时,就会开始进入Leader选举:
- 服务器初始化启动;
- 服务器运行期间无法和Leader保持连接。
- 而当一台机器进入Leader选举流程时,当前集群也可能会处于以下两种状态:
- 集群中本来就已经存在一个Leader
对于第一种已经存在Leader的情况,机器试图去选举Leader时,会被告知当前服务器的Leader信息,对于该机器来说,仅仅需要和Leader机器建立连接,并进行状态同步即可。 - 集群中确实不存在Leader
假设ZooKeeper由5台服务器组成,SID分别为1、2、3、4、5,ZXID分别为8、8、8、7、7,并且此时SID为3的服务器是Leader。某一时刻,3和5服务器出现故障,因此开始进行Leader选举。
选举规则:
- EPOCH大的直接胜出;
- EPOCH相同,事务id大的胜出;
- 事务id相同,服务器id大的胜出。
2、ZK 集群启动停止脚本
- 在 hadoop102 的
/home/atguigu/bin
目录下创建脚本
vim zk.sh
#!/bin/bash
case in
"start"){
for i in 192.168.196.128 192.168.196.129 192.168.196.130
do
echo ---------- zookeeper $i 启动 ------------
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"
done
}
;;
"stop"){
for i in 192.168.196.128 192.168.196.129 192.168.196.130
do
echo ---------- zookeeper $i 停止 ------------
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"
done
};;
"status"){
for i in 192.168.196.128 192.168.196.129 192.168.196.130
do
echo ---------- zookeeper $i 状态 ------------
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"
done
};;
esac
踩过的坑:
出现
Error: JAVA_HOME is not set and java could not be found in PATH.
,解决方法:vim /opt/module/zookeeper-3.5.7/bin/zkEnv.sh
添加自定义的
JAVA_HOME
:...... JAVA_HOME="/usr/local/jdk/jdk-9.0.1" if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then JAVA="$JAVA_HOME/bin/java" elif type -p java; then JAVA=java else echo "Error: JAVA_HOME is not set and java could not be found in PATH." 1>&2 exit 1 fi ......
添加这一行:
JAVA_HOME="/usr/local/jdk/jdk-9.0.1"
设置SSH免密登录:
- 客户端生成公私钥
ssh-keygen
上面这个命令会在用户目录.ssh文件夹下创建公私钥:
[root@zookeeper bin]# cd ~/.ssh/ [root@zookeeper .ssh]# ls id_rsa id_rsa.pub
- 上传公钥到服务器
192.168.196.128 这台主机要连接 192.168.196.129 和 192.168.196.130 这两台主机,因此需要把 192.168.196.128 主机生成的公钥上传到其他两台主机上,用户为 root:ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.196.129 ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.196.130
这里需要输入两台主机的密码。
- 测试免密登录
在 192.168.196.128 在台主机测试:[root@sunlight bin]# ssh root@192.168.196.129 Last login: Sat Apr 2 17:29:29 2022 from 192.168.196.1 [root@zookeeper ~]#
退出:
[root@zookeeper ~]# exit
- 增加脚本执行权限
chmod u+x zk.sh
- Zookeeper 集群查看状态脚本
[root@sunlight bin]# ./zk.sh status
---------- zookeeper 192.168.196.128 状态 ------------
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower
---------- zookeeper 192.168.196.129 状态 ------------
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader
---------- zookeeper 192.168.196.130 状态 ------------
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower
- Zookeeper 集群停止脚本
[root@sunlight bin]# ./zk.sh stop
---------- zookeeper 192.168.196.128 停止 ------------
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
---------- zookeeper 192.168.196.129 停止 ------------
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
---------- zookeeper 192.168.196.130 停止 ------------
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
- Zookeeper 集群启动脚本
[root@sunlight bin]# ./zk.sh start
---------- zookeeper 192.168.196.128 启动 ------------
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
---------- zookeeper 192.168.196.129 启动 ------------
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
---------- zookeeper 192.168.196.130 启动 ------------
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
3、客户端命令行操作
3.1、命令行语法
命令基本语法 | 功能描述 |
help | 显示所有操作命令 |
ls path | 使用 ls 命令来查看当前 znode 的子节点 [可监听] -w 监听子节点变化 -s 附加次级信息 |
create | 普通创建 -s 含有序列 -e 临时(重启或者超时消失) |
get path | 获得节点的值 [可监听] -w 监听节点内容变化 -s 附加次级信息 |
set | 设置节点的具体值 |
stat | 查看节点状态 |
delete | 删除节点 |
deleteall | 递归删除节点 |
- 启动客户端
bin/zkCli.sh -server 192.168.196.129:2181
- 显示所有操作命令
[zk: 192.168.196.129:2181(CONNECTED) 0] help
3.2、znode 节点数据信息
- 查看当前znode中所包含的内容
[zk: 192.168.196.129:2181(CONNECTED) 1] ls /
[zookeeper]
- 查看当前节点详细数据
[zk: 192.168.196.129:2181(CONNECTED) 2] ls -s /
[zookeeper]cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
- cZxid:创建节点的事务 zxid
每次修改 ZooKeeper 状态都会产生一个 ZooKeeper 事务 ID。事务 ID 是 ZooKeeper 中所有修改总的次序。每次修改都有唯一的 zxid,如果 zxid1 小于 zxid2,那么 zxid1 在 zxid2 之前发生。 - ctime:znode 被创建的毫秒数(从 1970 年开始)
- mzxid:znode 最后更新的事务 zxid
- mtime:znode 最后修改的毫秒数(从 1970 年开始)
- pZxid:znode 最后更新的子节点 zxid
- cversion:znode 子节点变化号,znode 子节点修改次数
- dataversion:znode 数据变化号
- aclVersion:znode 访问控制列表的变化号
- ephemeralOwner:如果是临时节点,这个是 znode 拥有者的 session id。如果不是临时节点则是 0
- dataLength:znode 的数据长度
- numChildren:znode 子节点数量
3.3、节点类型(持久/短暂/有序号/无序号)
- 持久(Persistent):客户端和服务器端断开连接后,创建的节点不删除
- 短暂(Ephemeral):客户端和服务器端断开连接后,创建的节点自己删除
说明:创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
注意:在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序
- 持久化目录节点
客户端与Zookeeper断开连接后,该节点依旧存在 - 持久化顺序编号目录节点
客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 - 临时目录节点
客户端与Zookeeper断开连接后,该节点被删除 - 临时顺序编号目录节点
客户端与Zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
操作:
- 分别创建2个普通节点(永久节点 + 不带序号)
[zk: 192.168.196.129:2181(CONNECTED) 3] create /sanguo "diaochan"
Created /sanguo
[zk: 192.168.196.129:2181(CONNECTED) 4] create /sanguo/shuguo "liubei"
Created /sanguo/shuguo
注意:创建节点时,要赋值
- 获得节点的值
[zk: 192.168.196.129:2181(CONNECTED) 5] get -s /sanguo
diaochan
cZxid = 0x400000002
ctime = Sat Apr 02 19:27:01 CST 2022
mZxid = 0x400000002
mtime = Sat Apr 02 19:27:01 CST 2022
pZxid = 0x400000003
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 1
[zk: 192.168.196.129:2181(CONNECTED) 6] get -s /sanguo/shuguo
liubei
cZxid = 0x400000003
ctime = Sat Apr 02 19:27:19 CST 2022
mZxid = 0x400000003
mtime = Sat Apr 02 19:27:19 CST 2022
pZxid = 0x400000003
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
- 创建带序号的节点(永久节点 + 带序号)
- 先创建一个普通的根节点 /sanguo/weiguo
[zk: 192.168.196.129:2181(CONNECTED) 7] create /sanguo/weiguo "caocao"
Created /sanguo/weiguo
- 创建带序号的节点
[zk: 192.168.196.129:2181(CONNECTED) 8] create -s /sanguo/weiguo/zhangliao "zhangliao"
Created /sanguo/weiguo/zhangliao0000000000
[zk: 192.168.196.129:2181(CONNECTED) 9] create -s /sanguo/weiguo/zhangliao "zhangliao"
Created /sanguo/weiguo/zhangliao0000000001
[zk: 192.168.196.129:2181(CONNECTED) 10] create -s /sanguo/weiguo/xuchu "xuchu"
Created /sanguo/weiguo/xuchu0000000002
如果原来没有序号节点,序号从0开始依次递增。如果原节点下已有2个节点,则再排序时从2开始,以此类推。
- 创建短暂节点(短暂节点 + 不带序号 or 带序号)
- 创建短暂的不带序号的节点
[zk: 192.168.196.129:2181(CONNECTED) 11] create -e /sanguo/wuguo "zhouyu"
Created /sanguo/wuguo
- 创建短暂的带序号的节点
[zk: 192.168.196.129:2181(CONNECTED) 12] create -e -s /sanguo/wuguo "zhouyu"
Created /sanguo/wuguo0000000003
- 在当前客户端是能查看到的
[zk: 192.168.196.129:2181(CONNECTED) 13] ls /sanguo
[shuguo, weiguo, wuguo, wuguo0000000003]
- 退出当前客户端然后再重启客户端
[zk: 192.168.196.129:2181(CONNECTED) 14] quit
bin/zkCli.sh
- 再次查看根目录下短暂节点已经删除
[zk: localhost:2181(CONNECTED) 0] ls /sanguo
[shuguo, weiguo]
- 修改节点数据值
[zk: localhost:2181(CONNECTED) 1] set /sanguo/weiguo "simayi"
3.4、监听器原理
客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、节点删除、子目录节点增加删除)时,ZooKeeper 会通知客户端。监听机制保证ZooKeeper保存的任何的数据的任何改变都能快速的响应到监听了该节点的应用程序。
3.4.1、监听器原理
- 监听原理详解
- 首先要有一个
main()
线程 - 在main线程中创建Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connet),一个负责监听(listener)
- 通过connect线程将注册的监听事件发送给Zookeeper
- 在Zookeeper的注册监听器列表中将注册的监听事件添加到列表中
- Zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程
- listener线程内部调用了process()方法
- 常见的监听
- 监听节点数据的变化
get path [watch]
- 监听子节点增减的变化
ls path [watch]
- 节点的值变化监听
- 在 hadoop104 主机上注册监听 /sanguo 节点数据变化
[zk: localhost:2181(CONNECTED) 0] get -w /sanguo
diaochan
- 在 hadoop103 主机上修改/sanguo 节点的数据
[zk: localhost:2181(CONNECTED) 0] set /sanguo "xishi"
- 观察 hadoop104 主机收到数据变化的监听
[zk: localhost:2181(CONNECTED) 1]
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/sanguo
注意:在hadoop103再多次修改/sanguo的值,hadoop104上不会再收到监听。因为注册一次,只能监听一次。想再次监听,需要再次注册。
- 节点的子节点变化监听(路径变化)
- 在 hadoop104 主机上注册监听 /sanguo 节点的子节点变化
[zk: localhost:2181(CONNECTED) 1] ls -w /sanguo
[shuguo, weiguo]
- 在 hadoop103 主机 /sanguo 节点上创建子节点
[zk: localhost:2181(CONNECTED) 1] create /sanguo/jin "simayi"
Created /sanguo/jin
- 观察 hadoop104 主机收到子节点变化的监听
[zk: localhost:2181(CONNECTED) 2]
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/sanguo
注意: 节点的路径变化,也是注册一次,生效一次。想多次生效,就需要多次注册。
3.5、节点删除与查看
- 删除节点
[zk: localhost:2181(CONNECTED) 2] delete /sanguo/jin
- 递归删除节点
[zk: localhost:2181(CONNECTED) 3] deleteall /sanguo/shuguo
- 查看节点状态
[zk: localhost:2181(CONNECTED) 4] stat /sanguo
cZxid = 0x400000002
ctime = Sat Apr 02 19:27:01 CST 2022
mZxid = 0x40000000f
mtime = Sat Apr 02 20:17:04 CST 2022
pZxid = 0x400000012
cversion = 9
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 1
4、客户端 API 操作
前提:保证 hadoop102、 hadoop103、 hadoop104 服务器上 Zookeeper 集群服务端启动。
4.1、IDEA 环境搭建
- 创建一个工程:zookeeper
- 添加pom文件
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
</dependency>
</dependencies>
- 拷贝
log4j.properties
文件到项目根目录
需要在项目的src/main/resources
目录下,新建一个文件,命名为 “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
- 创建包名zk
- 创建类名称zkClient
4.2、创建 ZooKeeper 客户端
@Before
public void init() throws IOException {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
//收到事件通知后的回调函数(用户的业务逻辑)
System.out.println(watchedEvent.getType() + "--"
+ watchedEvent.getPath());
// 再次启动监听
try {
List<String> children = zkClient.getChildren("/", true);
for (String child :
children) {
System.out.println(child);
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
4.3、创建子节点
//创建子节点
@Test
public void create() throws InterruptedException, KeeperException {
String nodeCreated = zkClient.create("/atguigu", "shuaige".getBytes(StandardCharsets.UTF_8),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
测试:在 hadoop102 的 zk 客户端上查看创建节点情况:
[zk: localhost:2181(CONNECTED) 0] get -s /atguigu
shuaige
cZxid = 0x400000017
ctime = Sat Apr 02 20:56:46 CST 2022
mZxid = 0x400000017
mtime = Sat Apr 02 20:56:46 CST 2022
pZxid = 0x400000017
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 0
4.4、获取子节点并监听节点变化
//获取子节点
@Test
public void getChilden() throws InterruptedException, KeeperException {
List<String> children = zkClient.getChildren("/", true);
System.out.println("==============================");
for (String child :
children) {
System.out.println(child);
}
//延时阻塞
Thread.sleep(Long.MAX_VALUE);
}
- 在 IDEA 控制台上看到如下节点:
zookeeper
sanguo
atguigu
- 在 hadoop102 的客户端上创建再创建一个节点 /atguigu1,观察 IDEA 控制台
[zk: localhost:2181(CONNECTED) 1] create /atguigu1 "atguigu1"
Created /atguigu1
- 在 hadoop102 的客户端上删除节点 /atguigu1,观察 IDEA 控制台
[zk: localhost:2181(CONNECTED) 2] delete /atguigu1
4.5、判断 Znode 是否存在
//判断 znode 是否存在
@Test
public void exits() throws InterruptedException, KeeperException {
Stat stat = zkClient.exists("/atguigu", false);
System.out.println(stat == null? "not exist" : "exist");
}
5、客户端向服务端写数据流程
5.1、写流程之写入请求直接发送给Leader节点
5.2、写流程之写入请求发送给follower节点