Zookeeper

Zookeeper

第 1 章 Zookeeper入门

1.1 概述

  • Zookeeper是一个开源的分布式的,为分布式框架提供协调服务的Apache项目。

  • Zookeeper工作机制

1.2 特点

1)Zookeeper:一个领导者(Leader),多个跟随者(Follower)组成的集群。
2)集群中只要有 半数以上节点存活,Zookeeper集群就能正常服务。所以Zookeeper适合安装奇数台服务器。
3)全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
4)更新请求顺序执行,来自同一个Client的更新请求按其发送顺序依次执行。
5)数据更新原子性,一次数据更新要么成功,要么失败。
6)实时性,在一定时间范围内,Client能读到最新数据。

1.3 数据结构

ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。

每一个ZNode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。

1.4 应用场景

  • 提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。

  • 统一命名服务

  • 统一配置管理

  • 统一集群管理

  • 服务器动态上下线

  • 软负载均衡

1.5 下载地址

1 )官网首页:

https://zookeeper.apache.org/

2 )下载截图



3 )下载 Linux 环境安装的 tar

第 2 章 Zookeeper本地安装

2.1 本地模式安装

1 )安装前准备

  • ( 1 )安装JDK

  • ( 2 )拷贝apache-zookeeper-3.5.7-bin.tar.gz安装包到Linux系统下

  • ( 3 )解压到指定目录

[atguigu@hadoop102 software]$ tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz - C /opt/module/
  • ( 4 )修改名称
[atguigu@hadoop102 module]$ mv apache-zookeeper-3.5.7 - bin/zookeeper-3.5.

2 )配置修改

  • ( 1 )将/opt/module/zookeeper-3.5.7/conf这个路径下的zoo_sample.cfg修改为zoo.cfg;
[atguigu@hadoop102 conf]$ mv zoo_sample.cfg zoo.cfg
  • ( 2 )打开zoo.cfg文件,修改dataDir路径:
[atguigu@hadoop102 zookeeper-3.5.7]$ vim zoo.cfg

修改如下内容:

dataDir=/opt/module/zookeeper-3.5.7/zkData
  • ( 3 )在/opt/module/zookeeper-3.5.7/这个目录上创建zkData文件夹
[atguigu@hadoop102 zookeeper-3.5.7]$ mkdir zkData

3 )操作 Zookeeper

  • ( 1 )启动Zookeeper
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh start
  • ( 2 )查看进程是否启动
[atguigu@hadoop102 zookeeper-3.5.7]$ jps
4020 Jps
4001 QuorumPeerMain
  • ( 3 )查看状态
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: standalone
  • ( 4 )启动客户端
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkCli.sh
  • ( 5 )退出客户端:
[zk: localhost:2181(CONNECTED) 0] quit
  • ( 6 )停止Zookeeper
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh stop

2.2 配置参数解读

  • Zookeeper中的配置文件zoo.cfg中参数含义解读如下:

  • 1 )tickTime = 2000 :通信心跳时间,Zookeeper服务器与客户端心跳时间,单位毫秒

  • 2 )initLimit = 10 :LF初始通信时限

Leader和Follower初始连接时能容忍的最多心跳数(tickTime的数量)
  • 3 )syncLimit = 5 :LF同步通信时限
Leader和Follower之间通信时间如果超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
  • 4 )dataDir:保存Zookeeper中的数据
注意:默认的tmp目录,容易被Linux系统定期删除,所以一般不用默认的tmp目录。
  • 5 )clientPort = 2181 :客户端连接端口,通常不做修改。

第 3 章 Zookeeper集群操作

3 .1 集群操作

3 .1.1 集群安装

  • 1 )集群规划
在hadoop102、hadoop103和hadoop104三个节点上都部署Zookeeper。

思考:如果是 10 台服务器,需要部署多少台 Zookeeper ?
  • 2 )解压安装
    ( 1 )在hadoop102解压Zookeeper安装包到/opt/module/目录下
[atguigu@hadoop102 software]$ tar -zxvf apache-zookeeper-3.5.7-
bin.tar.gz - C /opt/module/

( 2 )修改apache-zookeeper-3.5.7-bin名称为zookeeper-3.5.

[atguigu@hadoop102 module]$ mv apache-zookeeper-3.5.7-bin/
zookeeper-3.5.
  • 3 )配置服务器编号
    ( 1 )在/opt/module/zookeeper-3.5.7/这个目录下创建zkData
[atguigu@hadoop102 zookeeper-3.5.7]$ mkdir zkData

( 2 )在/opt/module/zookeeper-3.5.7/zkData目录下创建一个myid的文件

[atguigu@hadoop102 zkData]$ vi myid

在文件中添加与server对应的编号(注意:上下不要有空行,左右不要有空格)

注意:添加myid文件,一定要在Linux里面创建,在notepad++里面很可能乱码

( 3 )拷贝配置好的zookeeper到其他机器上

[atguigu@hadoop102 module ]$ xsync zookeeper-3.5.

并分别在hadoop10 3 、hadoop10 4 上修改myid文件中内容为 3 、 4

4 )配置zoo.cfg文件

( 1 )重命名/opt/module/zookeeper-3.5.7/conf这个目录下的zoo_sample.cfg为zoo.cfg

[atguigu@hadoop102 conf]$ mv zoo_sample.cfg zoo.cfg

( 2 )打开zoo.cfg文件

[atguigu@hadoop102 conf]$ vim zoo.cfg

修改数据存储路径配置

dataDir=/opt/module/zookeeper-3.5.7/zkData

增加如下配置
cluster
server.2=hadoop102:2888:
server.3=hadoop103:2888:
server.4=hadoop104:2888:
( 3 )配置参数解读

server.A=B:C:D。
A 是一个数字,表示这个是第几号服务器;

集群模式下配置一个文件myid,这个文件在dataDir目录下,这个文件里面有一个数据

就是A的值,Zookeeper启动时读取此文件,拿到里面的数据与zoo.cfg里面的配置信息比

较从而判断到底是哪个server。

B 是这个服务器的地址;

C 是这个服务器Follower与集群中的Leader服务器交换信息的端口;

D 是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的

Leader,而这个端口就是用来执行选举时服务器相互通信的端口。

( 4 )同步zoo.cfg配置文件
[atguigu@hadoop102 conf]$ xsync zoo.cfg

5 )集群操作

( 1 )分别启动Zookeeper
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh start

[atguigu@hadoop103 zookeeper-3.5.7]$ bin/zkServer.sh start
[atguigu@hadoop104 zookeeper-3.5.7]$ bin/zkServer.sh start

( 2 )查看状态
[atguigu@hadoop102 zookeeper-3.5.7] bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: follower
[atguigu@hadoop103 zookeeper-3.5.7] bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: leader
[atguigu@hadoop104 zookeeper-3.4.5] bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: follower

3.1.2 选举机制(面试重点)

Zookeeper选举机制——第一次启动

Server
myid=

Server
myid=

Server
myid=

Server
myid=

Server
myid=

follower follower leader follower follower

Client

Zookeeper Service Client
每次写操作都有
事务id(zxid)

( 1 )服务器 1 启动,发起一次选举。服务器 1 投自己一票。此时服务器 1 票数一票,不够半数以上( 3 票),选举无法完成,服务器 1 状态保持为
LOOKING;
( 2 )服务器 2 启动,再发起一次选举。服务器 1 和 2 分别投自己一票并交换选票信息:此时服务器 1 发现服务器 2 的myid比自己目前投票推举的(服务器 1 )
大,更改选票为推举服务器 2 。此时服务器 1 票数 0 票,服务器 2 票数 2 票,没有半数以上结果,选举无法完成,服务器 1 , 2 状态保持LOOKING
( 3 )服务器 3 启动,发起一次选举。此时服务器 1 和 2 都会更改选票为服务器 3 。此次投票结果:服务器 1 为 0 票,服务器 2 为 0 票,服务器 3 为 3 票。此时服
务器 3 的票数已经超过半数,服务器 3 当选Leader。服务器 1 , 2 更改状态为FOLLOWING,服务器 3 更改状态为LEADING;

LOOKING LOOKING
1 0 1 20 3

( 4 )服务器 4 启动,发起一次选举。此时服务器 1 , 2 , 3 已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器 3 为 3 票,服务器 4 为
1 票。此时服务器 4 服从多数,更改选票信息为服务器 3 ,并更改状态为FOLLOWING;
( 5 )服务器 5 启动,同 4 一样当小弟。

SID:服务器ID。用来唯一标识一台
ZooKeeper集群中的机器,每台机器不能重
复,和myid一致。
ZXID:事务ID。ZXID是一个事务ID,用来
标识一次服务器状态的变更。在某一时刻,
集群中的每台机器的ZXID值不一定完全一
致,这和ZooKeeper服务器对于客户端“更
新请求”的处理逻辑有关。
Epoch:每个Leader任期的代号。没有
Leader时同一轮投票过程中的逻辑时钟值是
相同的。每投完一次票这个数据就会增加

Zookeeper选举机制——非第一次启动

Server
myid=

Server
myid=

Server
myid=

Server
myid=

Server
myid=

follower follower leader follower follower

Client

Zookeeper Service Client
每次写操作都有
事务id(zxid)

( 1 )当ZooKeeper集群中的一台服务器出现以下两种情况之一时,就会开始进入Leader选举:

SID:服务器ID。用来唯一标识一台
ZooKeeper集群中的机器,每台机器不能重
复,和myid一致。
ZXID:事务ID。ZXID是一个事务ID,用来
标识一次服务器状态的变更。在某一时刻,
集群中的每台机器的ZXID值不一定完全一
致,这和ZooKeeper服务器对于客户端“更
新请求”的处理逻辑有关。
Epoch:每个Leader任期的代号。没有
Leader时同一轮投票过程中的逻辑时钟值是
相同的。每投完一次票这个数据就会增加

  • 服务器初始化启动。
  • 服务器运行期间无法和Leader保持连接。
    ( 2 )而当一台机器进入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,ZXID,SID)
    SID为 1 、 2 、 4 的机器投票情况:( 1 , 8 , 1 ) ( 1 , 8 , 2 ) ( 1 , 7 , 4 )

(EPOCH,ZXID,SID)(EPOCH,ZXID,SID)

选举Leader规则:①EPOCH大的直接胜出 ②EPOCH相同,事务id大的胜出 ③事务id相同,服务器id大的胜出

3.1.3 ZK 集群启动停止脚本

1 )在hadoop102的/home/atguigu/bin目录下创建脚本
[atguigu@hadoop102 bin]$ vim zk.sh

在脚本中编写如下内容

!/bin/bash

case $1 in
"start"){
for i in hadoop102 hadoop103 hadoop
do
echo ---------- zookeeper $i 启动 ------------
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh
start"
done
};;
"stop"){
for i in hadoop102 hadoop103 hadoop
do
echo ---------- zookeeper $i 停止 ------------
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh
stop"
done
};;
"status"){
for i in hadoop102 hadoop103 hadoop
do
echo ---------- zookeeper $i 状态 ------------
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh
status"
done
};;
esac

2 )增加脚本执行权限
[atguigu@hadoop102 bin]$ chmod u+x zk.sh

3 )Zookeeper集群启动脚本
[atguigu@hadoop102 module]$ zk.sh start
4 )Zookeeper集群停止脚本
[atguigu@hadoop102 module]$ zk.sh stop

3. 2 客户端命令行操作

3.2.1 命令行语法

命令基本语法 功能描述

help 显示所有操作命令
ls path 使用 ls 命令来查看当前znode的子节点 [可监听]

  • w 监听子节点变化
  • s 附加次级信息

create 普通创建

  • s 含有序列
  • e 临时(重启或者超时消失)

get path (^) 获得节点的值 [可监听]

  • w 监听节点内容变化
  • s 附加次级信息
    set 设置节点的具体值
    stat 查看节点状态
    delete 删除节点
    deleteall 递归删除节点

1 )启动客户端

[atguigu@hadoop10 2 zookeeper-3.5.7]$ bin/zkCli.sh - server
hadoop 102 :

2 )显示所有操作命令

[zk: hadoop102:2181(CONNECTED) 1] help

3.2.2 znode 节点数据信息

1 )查看当前znode中所包含的内容

[zk: hadoop102:2181(CONNECTED) 0] ls /
[zookeeper]

2 )查看当前节点详细数据

[zk: hadoop102:2181(CONNECTED) 5] ls -s /
[zookeeper]cZxid = 0x
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x
cversion = - 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x
dataLength = 0
numChildren = 1

( 1 )czxid:创建节点的事务zxid

每次修改ZooKeeper状态都会产生一个ZooKeeper事务ID。事务ID是ZooKeeper中所

有修改总的次序。每次修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之

前发生。

( 2 )ctime:znode被创建的毫秒数(从 1970 年开始)

( 3 )mzxid:znode最后更新的事务zxid

( 4 )mtime:znode最后修改的毫秒数(从 1970 年开始)

( 5 )pZxid:znode最后更新的子节点zxid

( 6 )cversion:znode子节点变化号,znode子节点修改次数

( 7 )dataversion:znode数据变化号

( 8 )aclVersion:znode访问控制列表的变化号

( 9 )ephemeralOwner:如果是临时节点,这个是znode拥有者的session id。如果不是

临时节点则是 0 。

( 10 )dataLength:znode的数据长度

( 11 )numChildren:znode子节点数量

    1. 3 节点类型(持久 / 短暂 / 有序号 / 无序号)

节点类型

短暂(Ephemeral):客户端和服务器端断开连接后,创建的节点自己删除

持久(Persistent):客户端和服务器端断开连接后,创建的节点不删除

/

/znode1 /znode4_
Persistent

Client1 Client2 Client3 Client

/znode2_001 /znode
Persistent_sequential Ephemeral Ephemeral_sequential

( 1 )持久化目录节点
客户端与Zookeeper断开连接后,该节点依旧存在

( 2 )持久化顺序编号目录节点
客户端与Zookeeper断开连接后,该节点依旧存
在,只是Zookeeper给该节点名称进行顺序编号

( 3 )临时目录节点
客户端与Zookeeper断开连接后,该节点被删除

( 4 )临时顺序编号目录节点
客户端与Zookeeper断开连接后,该节点被删除,只是
Zookeeper给该节点名称进行顺序编号。

说明:创建znode时设置顺序标识,znode名称
后会附加一个值,顺序号是一个单调递增的计数
器,由父节点维护
注意:在分布式系统中,顺序号可以被用于
为所有的事件进行全局排序,这样客户端可以通
过顺序号推断事件的顺序

Servers

1 )分别创建 2 个普通节点(永久节点 + 不带序号)

[zk: localhost:2181(CONNECTED) 3] create /sanguo "diaochan"
Created /sanguo
[zk: localhost:2181(CONNECTED) 4] create /sanguo/shuguo
"liubei"
Created /sanguo/shuguo

注意:创建节点时,要赋值

2 )获得节点的值

[zk: localhost:2181(CONNECTED) 5] get -s /sanguo
diaochan
cZxid = 0x
ctime = Wed Aug 29 00:03:23 CST 2018
mZxid = 0x
mtime = Wed Aug 29 00:03:23 CST 2018
pZxid = 0x
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x

dataLength = 7
numChildren = 1

[zk: localhost:2181(CONNECTED) 6] get -s /sanguo/shuguo
liubei
cZxid = 0x
ctime = Wed Aug 29 00:04:35 CST 2018
mZxid = 0x
mtime = Wed Aug 29 00:04:35 CST 2018
pZxid = 0x
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x
dataLength = 6
numChildren = 0

3 )创建带序号的节点(永久节点 + 带序号)

( 1 )先创建一个普通的根节点/sanguo/weiguo

[zk: localhost:2181(CONNECTED) 1] create /sanguo/weiguo
"caocao"
Created /sanguo/weiguo

( 2 )创建带序号的节点

[zk: localhost:2181(CONNECTED) 2] create -s
/sanguo/weiguo/zhangliao "zhangliao"
Created /sanguo/weiguo/zhangliao 0000000000

[zk: localhost:2181(CONNECTED) 3] create -s
/sanguo/weiguo/zhangliao "zhangliao"
Created /sanguo/weiguo/zhangliao 0000000001

[zk: localhost:2181(CONNECTED) 4] create -s
/sanguo/weiguo/xuchu "xuchu"
Created /sanguo/weiguo/xuchu 0000000002
如果原来没有序号节点,序号从 0 开始依次递增。如果原节点下已有 2 个节点,则再排

序时从 2 开始,以此类推。

4 )创建短暂节点(短暂节点 + 不带序号 or 带序号)

( 1 )创建短暂的不带序号的节点
[zk: localhost:2181(CONNECTED) 7] create -e /sanguo/wuguo
"zhouyu"
Created /sanguo/wuguo

( 2 )创建短暂的带序号的节点
[zk: localhost:2181(CONNECTED) 2] create -e -s /sanguo/wuguo
"zhouyu"
Created /sanguo/wuguo000000000 1

( 3 )在当前客户端是能查看到的
[zk: localhost:2181(CONNECTED) 3] ls /sanguo
[wuguo, wuguo0000000001, shuguo]

( 4 )退出当前客户端然后再重启客户端

[zk: localhost:2181(CONNECTED) 12] quit
[atguigu@hadoop104 zookeeper-3.5.7]$ bin/zkCli.sh

( 5 )再次查看根目录下短暂节点已经删除
[zk: localhost:2181(CONNECTED) 0] ls /sanguo
[shuguo]

5 )修改节点数据值

[zk: localhost:2181(CONNECTED) 6] set /sanguo/weiguo "simayi"

3. 2. 4 监听器原理

客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、节点删除、子目

录节点增加删除)时,ZooKeeper会通知客户端。监听机制保证ZooKeeper保存的任何的数

据的任何改变都能快速的响应到监听了该节点的应用程序。

监听器原理

1 Main()线程
2 创建zkClient
Listener

connect

注册的监听器列表
4 Client:ip:port:/path
port

6 process() 3 getChildren(“/”,true)

5 “/”路径数据发生变化

1 、监听原理详解 2 、常见的监听
1 )首先要有一个main()线程
2 )在main线程中创建Zookeeper客户端,这时就会创建两个线
程,一个负责网络连接通信(connet),一个负责监听(listener)。
3 )通过connect线程将注册的监听事件发送给Zookeeper。
4 )在Zookeeper的注册监听器列表中将注册的监听事件添加到列表中。
5 )Zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程。
6 )listener线程内部调用了process()方法。

1 )监听节点数据的变化
getpath[watch]

2 )监听子节点增减的变化
lspath[watch]

ZK 客户端 ZK 服务端

1 )节点的值变化监听

( 1 )在hadoop 104 主机上注册监听/sanguo节点数据变化

[zk: localhost:2181(CONNECTED) 26] get -w /sanguo

( 2 )在hadoop 103 主机上修改/sanguo节点的数据

[zk: localhost:2181(CONNECTED) 1] set /sanguo "xisi"

( 3 )观察hadoop 104 主机收到数据变化的监听

WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged
path:/sanguo

注意:在hadoop 103 再多次修改/sanguo的值,hadoop 104 上不会再收到监听。因为注册

一次,只能监听一次。想再次监听,需要再次注册。

2 )节点的子节点变化监听(路径变化)

( 1 )在hadoop 104 主机上注册监听/sanguo节点的子节点变化

[zk: localhost:2181(CONNECTED) 1] ls -w /sanguo
[shuguo, weiguo]

( 2 )在hadoop 103 主机/sanguo节点上创建子节点

[zk: localhost:2181(CONNECTED) 2] create /sanguo/jin "simayi"
Created /sanguo/jin

( 3 )观察hadoop 104 主机收到子节点变化的监听

WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged
path:/sanguo
注意:节点的路径变化,也是注册一次,生效一次。想多次生效,就需要多次注册。

3.2.5 节点删除与查看

1 )删除节点

[zk: localhost:2181(CONNECTED) 4] delete /sanguo/jin

2 )递归删除节点

[zk: localhost:2181(CONNECTED) 15] deleteall /sanguo/shuguo

3 )查看节点状态

[zk: localhost:2181(CONNECTED) 17] stat /sanguo
cZxid = 0x
ctime = Wed Aug 29 00:03:23 CST 2018
mZxid = 0x
mtime = Wed Aug 29 00:21:23 CST 2018
pZxid = 0x
cversion = 9
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x
dataLength = 4
numChildren = 1

  1. 3 客户端 API 操作

前提:保证hadoop 102 、hadoop 103 、hadoop 104 服务器上Zookeeper集群服务端启动。

  1. 3 .1 IDEA 环境搭建

1 )创建一个工程:zookeeper

2 )添加pom文件

junit junit RELEASE org.apache.logging.log4j log4j-core 2.8.2 org.apache.zookeeper zookeeper 3.5.7

3 )拷贝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

4 )创建包名com.atguigu.zk

5 )创建类名称zkClient

3. 3 .2 创建 ZooKeeper 客户端

// 注意:逗号前后不能有空格

private static String connectString =
"hadoop102:2181,hadoop103:2181,hadoop104:2181";

private static int sessionTimeout = 2000;
private ZooKeeper zkClient = null;

@Before
public void init () throws Exception {

zkClient = new ZooKeeper(connectString, sessionTimeout, new
Watcher() {

@Override
public void process(WatchedEvent watchedEvent) {

// 收到事件通知后的回调函数(用户的业务逻辑)

System.out.println(watchedEvent.getType() + "--"

  • watchedEvent.getPath());

// 再次启动监听

try {
List children = zkClient.getChildren("/",
true);
for (String child : children) {
System.out.println(child);
}

} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}

  1. 3 .3 创建子节点

// 创建子节点

@Test
public void create () throws Exception {

// 参数 1 :要创建的节点的路径; 参数 2 :节点数据 ; 参数 3 :节点权限 ;

参数 4 :节点的类型

String nodeCreated = zkClient.create("/atguigu",
"shuaige".getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
测试:在hadoop 102 的zk客户端上查看创建节点情况
[zk: localhost:2181(CONNECTED) 16] get -s /atguigu
shuaige

3. 3 .4 获取子节点并监听节点变化

// 获取子节点

@Test
public void getChildren () throws Exception {

List children = zkClient.getChildren("/", true);

for (String child : children) {
System.out.println(child);
}

// 延时阻塞

Thread.sleep(Long.MAX_VALUE);
}

( 1 )在IDEA控制台上看到如下节点:
zookeeper
sanguo
atguigu

( 2 )在hadoop102的客户端上创建再创建一个节点/atguigu 1 ,观察IDEA控制台
[zk: localhost:2181(CONNECTED) 3] create /atguigu1 "atguigu1"
( 3 )在hadoop102的客户端上删除节点/atguigu 1 ,观察IDEA控制台
[zk: localhost:2181(CONNECTED) 4] delete /atguigu

3. 3 .5 判断 Znode 是否存在

// 判断znode是否存在
@Test
public void exist () throws Exception {

Stat stat = zkClient.exists("/atguigu", false);

System.out.println(stat == null - "not exist" : "exist");
}

3 .4 客户端向服务端写数据流程

Client ZK ServerFollower

ZK Server
Leader

ZK Server
Follower

2 write

1 write

3 ack
4 ack

5 write

6 ack

写流程之写入请求直接发送给 Leader 节点

Client ZK ServerFollower

ZK Server
Leader

ZK Server
Follower

1 write

写流程之写入请求发送给 follower 节点

5 ack 2 write请求 4 ack 3 write

6 ack

7 write 8 ack

第 4 章 服务器动态上下线监听案例

4.1 需求

某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知

到主节点服务器的上下线。

4.2 需求分析

服务器动态上下线

业务
功能

服务器 1

客户端能实时洞察到服务
器上下线的变化
业务
功能

服务器 2
业务
功能

服务器 3

客户端 1 客户端 2 客户端 3

Zookeeper集群
1 服务端启动时去注册信
/servers/server 1 hadoop 10180 nodes 息(创建都是临时节点)

2 获取到当前在线服务器列
表,并且注册监听

3 服务器节点下线

4 服务器节点上
下线事件通知^5 process(){
重新再去获取服务器
列表,并注册监听
}

/server 2 hadoop 10290 nodes
/server3 hadoop103 95 nodes

4.3 具体实现

( 1 )先在集群上创建/servers节点
[zk: localhost:2181(CONNECTED) 10] create /servers "servers"
Created /servers

( 2 )在Idea中创建包名:com.atguigu.zkcase

( 3 )服务器端向Zookeeper注册代码
package com.atguigu.zkcase 1 ;
import java.io.IOException;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;

public class DistributeServer {

private static String connectString =
"hadoop102:2181,hadoop103:2181,hadoop104:2181";
private static int sessionTimeout = 2000;
private ZooKeeper zk = null;
private String parentNode = "/servers";

// 创建到 zk 的客户端连接
public void getConnect() throws IOException{

zk = new ZooKeeper(connectString, sessionTimeout, new
Watcher() {

@Override
public void process(WatchedEvent event) {

}

});

}

// 注册服务器

public void registServer(String hostname) throws Exception{

String create = zk.create(parentNode + "/server",
hostname.getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);

System.out.println(hostname +" is online "+ create);
}

// 业务功能

public void business(String hostname) throws Exception{
System.out.println(hostname + " is working ...");

Thread.sleep(Long.MAX_VALUE);
}

public static void main(String[] args) throws Exception {

// 1 获取 zk 连接
DistributeServer server = new DistributeServer();
server.getConnect();

// 2 利用 zk 连接注册服务器信息
server.registServer(args[0]);

// 3 启动业务功能

server.business(args[0]);
}
}

( 3 )客户端代码
package com.atguigu.zkcase 1 ;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

public class DistributeClient {

private static String connectString =
"hadoop102:2181,hadoop103:2181,hadoop104:2181";
private static int sessionTimeout = 2000;
private ZooKeeper zk = null;
private String parentNode = "/servers";

// 创建到 zk 的客户端连接
public void getConnect() throws IOException {

zk = new ZooKeeper(connectString, sessionTimeout, new
Watcher() {

@Override
public void process(WatchedEvent event) {

// 再次启动监听

try {
getServerList();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}

// 获取服务器列表信息

public void getServerList() throws Exception {

// 1 获取服务器子节点信息,并且对父节点进行监听

List children = zk.getChildren(parentNode, true);

// 2 存储服务器信息列表

ArrayList servers = new ArrayList<>();

// 3 遍历所有节点,获取节点中的主机名称信息

for (String child : children) {
byte[] data = zk.getData(parentNode + "/" + child,
false, null);

servers.add(new String(data));
}

// 4 打印服务器列表信息

System.out.println(servers);
}

// 业务功能

public void business() throws Exception{

System.out.println("client is working ...");
Thread.sleep(Long.MAX_VALUE);
}

public static void main(String[] args) throws Exception {

// 1 获取 zk 连接
DistributeClient client = new DistributeClient();
client.getConnect();

// 2 获取 servers 的子节点信息,从中获取服务器信息列表
client.getServerList();

// 3 业务进程启动

client.business();
}
}

4.4 测试

1 )在Linux命令行上操作增加减少服务器

( 1 )启动DistributeClient客户端

( 2 )在hadoop 102 上zk的客户端/servers目录上创建临时带序号节点
[zk: localhost:2181(CONNECTED) 1] create -e -s
/servers/hadoop102 "hadoop102"

[zk: localhost:2181(CONNECTED) 2 ] create -e -s
/servers/hadoop103 "hadoop103"

( 3 )观察Idea控制台变化
[hadoop102, hadoop103]

( 4 )执行删除操作

[zk: localhost:2181(CONNECTED) 8] delete
/servers/hadoop102000000000 0

( 5 )观察Idea控制台变化
[hadoop103]

2 )在Idea上操作增加减少服务器

( 1 )启动DistributeClient客户端(如果已经启动过,不需要重启)

( 2 )启动DistributeServer服务

①点击Edit Configurations...

②在弹出的窗口中(Program arguments)输入想启动的主机,例如,hadoop 102

③回到DistributeServer 的 main 方法,右键,在弹出的窗口中点击 Run

“DistributeServer.main()”

④观察DistributeServer控制台,提示hadoop102 is working

⑤观察DistributeClient控制台,提示hadoop102已经上线

第 5 章 ZooKeeper分布式锁案例

什么叫做分布式锁呢?

比如说"进程1"在使用该资源的时候,会先去获得锁,"进程1"获得锁以后会对该资源

保持独占,这样其他进程就无法访问该资源,"进程1"用完该资源以后就将锁释放掉,让其

他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的

访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。

Client

Client

Client

/locks

/seq- 000000000

/seq- 000000001

/seq- 000000002

/seq- 000000003

create -e -s /locks/seq-
创建临时顺序节点

watch

watch

watch

1 )接收到请求后,在/locks节点下创建一个临时顺序节点

2 )判断自己是不是当前节点下最小的节点:是,获取到
锁;不是,对前一个节点进行监听

3 )获取到锁,处理完业务后,delete节点释放锁,然后
下面的节点将收到通知,重复第二步判断

获取到锁后,处理业务

分布式锁案例分析

5. 1 原生 Zookeeper 实现分布式锁案例

1 )分布式锁实现

package com.atguigu.lock2;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class DistributedLock {

// zookeeper server列表
private String connectString =
"hadoop102:2181,hadoop103:2181,hadoop104:2181";
// 超时时间
private int sessionTimeout = 2000;

private ZooKeeper zk;

private String rootNode = "locks";
private String subNode = "seq-";
// 当前client等待的子节点
private String waitPath;

//ZooKeeper连接
private CountDownLatch connectLatch = new CountDownLatch(1);

//ZooKeeper节点等待
private CountDownLatch waitLatch = new CountDownLatch(1);

// 当前client创建的子节点
private String currentNode;

// 和zk服务建立连接,并创建根节点
public DistributedLock() throws IOException,
InterruptedException, KeeperException {

zk = new ZooKeeper(connectString, sessionTimeout, new
Watcher() {
@Override
public void process(WatchedEvent event) {
// 连接建立时, 打开latch, 唤醒wait在该latch上的线程
if (event.getState() ==
Event.KeeperState.SyncConnected) {
connectLatch.countDown();
}

// 发生了waitPath的删除事件
if (event.getType() ==
Event.EventType.NodeDeleted && event.getPath().equals(waitPath))
{
waitLatch.countDown();
}
}
});

// 等待连接建立

connectLatch.await();

//获取根节点状态

Stat stat = zk.exists("/" + rootNode, false);

//如果根节点不存在,则创建根节点,根节点类型为永久节点

if (stat == null) {
System.out.println("根节点不存在");
zk.create("/" + rootNode, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}

// 加锁方法

public void zkLock() {

try {
//在根节点下创建临时顺序节点,返回值为创建的节点路径
currentNode = zk.create("/" + rootNode + "/" + subNode,
null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);

// wait一小会, 让结果更清晰一些
Thread.sleep(10);

// 注意, 没有必要监听"/locks"的子节点的变化情况

List childrenNodes = zk.getChildren("/" +
rootNode, false);

// 列表中只有一个子节点, 那肯定就是 currentNode , 说明
client获得锁
if (childrenNodes.size() == 1) {
return;
} else {
//对根节点下的所有临时顺序节点进行从小到大排序
Collections.sort(childrenNodes);

//当前节点名称

String thisNode = currentNode.substring(("/" +
rootNode + "/").length());
//获取当前节点的位置
int index = childrenNodes.indexOf(thisNode);

if (index == -1) {
System.out.println("数据异常");
} else if (index == 0) {
// index == 0, 说明 thisNode在列表中最小, 当前
client获得锁
return;
} else {
// 获得排名比currentNode 前 1 位的节点
this.waitPath = "/" + rootNode + "/" +
childrenNodes.get(index - 1);

// 在waitPath上注册监听器, 当waitPath被删除时,
zookeeper会回调监听器的process方法
zk.getData(waitPath, true, new Stat());
//进入等待锁状态
waitLatch.await();

return;
}
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

// 解锁方法

public void zkUnlock() {
try {
zk.delete(this.currentNode, -1);
} catch (InterruptedException | KeeperException e) {
e.printStackTrace();
}
}
}

2 )分布式锁测试

( 1 )创建两个线程

package com.atguigu.lock2;

import org.apache.zookeeper.KeeperException;

import java.io.IOException;

public class DistributedLockTest {

public static void main(String[] args) throws
InterruptedException, IOException, KeeperException {

// 创建分布式锁 1

final DistributedLock lock1 = new DistributedLock();
// 创建分布式锁 2
final DistributedLock lock2 = new DistributedLock();

new Thread(new Runnable() {
@Override
public void run() {
// 获取锁对象
try {
lock1.zkLock();
System.out.println("线程 1 获取锁");
Thread.sleep(5 * 1000);

lock1.zkUnlock();
System.out.println("线程 1 释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
// 获取锁对象
try {
lock2.zkLock();
System.out.println("线程 2 获取锁");
Thread.sleep(5 * 1000);

lock2.zkUnlock();
System.out.println("线程 2 释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}

( 2 )观察控制台变化:

线程 1 获取锁

线程 1 释放锁

线程 2 获取锁

线程 2 释放锁

  1. 2 Curator 框架实现分布式锁案例

1 )原生的 Java API 开发存在的问题

( 1 )会话连接是异步的,需要自己去处理。比如使用CountDownLatch

( 2 )Watch需要重复注册,不然就不能生效

( 3 )开发的复杂性还是比较高的

( 4 )不支持多节点删除和创建。需要自己去递归

2Curator 是一个专门解决分布式锁的框架,解决了原生 Java API 开发分布式遇到的问题。

详情请查看官方文档:https://curator.apache.org/index.html

3Curator 案例实操

( 1 )添加依赖

org.apache.curator
curator-framework
4.3.0


org.apache.curator
curator-recipes
4.3.0


org.apache.curator
curator-client
4.3.0

( 2 )代码实现
package com.atguigu.lock;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import
org.apache.curator.framework.recipes.locks.InterProcessLock;
import
org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorLockTest {

private String rootNode = "/locks";

// zookeeper server列表
private String connectString =
"hadoop102:2181,hadoop103:2181,hadoop104:2181";

// connection超时时间
private int connectionTimeout = 2 000;

// session超时时间
private int sessionTimeout = 2 000;

public static void main(String[] args) {

new CuratorLockTest().test();
}

// 测试

private void test() {

// 创建分布式锁 1

final InterProcessLock lock1 = new
InterProcessMutex(getCuratorFramework(), rootNode);

// 创建分布式锁 2

final InterProcessLock lock2 = new
InterProcessMutex(getCuratorFramework(), rootNode);

new Thread(new Runnable() {
@Override
public void run() {
// 获取锁对象
try {
lock1.acquire();
System.out.println("线程 1 获取锁");
// 测试锁重入
lock1.acquire();
System.out.println("线程 1 再次获取锁");
Thread.sleep(5 * 1000);
lock1.release();
System.out.println("线程 1 释放锁");
lock1.release();
System.out.println("线程 1 再次释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// 获取锁对象
try {
lock2.acquire();
System.out.println("线程 2 获取锁");
// 测试锁重入
lock2.acquire();

System.out.println("线程 2 再次获取锁");
Thread.sleep(5 * 1000);
lock2.release();
System.out.println("线程 2 释放锁");
lock2.release();
System.out.println("线程 2 再次释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}

// 分布式锁初始化

public CuratorFramework getCuratorFramework (){

//重试策略,初试时间 3 秒,重试 3 次

RetryPolicy policy = new ExponentialBackoffRetry(3000, 3);

//通过工厂创建Curator
CuratorFramework client =
CuratorFrameworkFactory.builder()
.connectString(connectString)
.connectionTimeoutMs(connectionTimeout)
.sessionTimeoutMs(sessionTimeout)
.retryPolicy(policy).build();

//开启连接

client.start();
System.out.println("zookeeper 初始化完成...");
return client;
}

}

( 2 )观察控制台变化:

线程 1 获取锁

线程 1 再次获取锁

线程 1 释放锁

线程 1 再次释放锁

线程 2 获取锁

线程 2 再次获取锁

线程 2 释放锁

线程 2 再次释放锁

第 6 章 企业面试真题(面试重点)

6 .1 选举机制

半数机制,超过半数的投票通过,即通过。

( 1 )第一次启动选举规则:

投票过半数时,服务器id大的胜出

( 2 )第二次启动选举规则:

①EPOCH大的直接胜出

②EPOCH相同,事务id大的胜出

③事务id相同,服务器id大的胜出

6 .2 生产集群安装多少 zk 合适?

安装奇数台。

生产经验:

  • 10 台服务器: 3 台zk;

  • 20 台服务器: 5 台zk;

  • 100 台服务器: 11 台zk;

  • 200 台服务器: 11 台zk

服务器台数多:好处,提高可靠性;坏处:提高通信延时

6. 3 常用命令

ls、get、create、delete

posted @   城市幽灵  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示