-
概述
zookeeper是一个开源的、分布式的、为分布式应用提供协调服务的Apache项目. -
zookeeper的工作机制
从设计模式角度理解: zookeeper是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,zookeeper就将负责通知已经在zookeeper上注册的那些观察者做出相应的反应. -
zookeeper特点
- 一个领导者leader,多个跟随者follower组成的集群
- 集群中只要有半数以上节点存活,zookeeper集群就能正常服务
- 全局数据一致: 每个server保存一份相同的数据副本,client无论连接到哪一个server,数据都是一致的
- 更新请求顺序进行: 来自同一个client的更新请求按其发送顺序依次执行
- 数据更新原子性: 一次数据更新要么成功,要么失败
- 实时性: 在一定时间范围内,client能读到最新的数据(zookeeper中存储的数据很少,同步很快).
- zookeeper数据结构
zookeeper数据模型的结构和unix文件系统类似,整体可以看作是一棵树,每一个节点称作一个Znode.每一个Znode默认能够存储1MB的数据,每个Znode都可以通过其路径唯一标识.
- zookeeper应用场景
-
统一命名服务(包含进统一配置管理)
在分布式环境下,经常需要对应用/服务进行统一命名,便于识别
例如: 一个集群一个统一名称 -
统一配置管理
可将配置信息写入zookeeper上的一个Znode,各个客户端服务器监听这个Znode,一旦Znode中的数据修改,zookeeper将通知各个客户端服务器. -
统一集群管理
zookeeper可以实现实时监控节点状态变化.可将节点信息写入zookeeper上的一个Znode,监听这个Znode可以获取它的实时状态变化. -
服务器节点动态上下线(包含进统一集群管理)
客户端能实时洞察到服务器上下线的变化
-
软负载均衡
在zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求.
- zookeeper的安装
- 安装jdk
# 更新软件包列表
sudo apt-get update
# 安装openjdk-8-jdk
sudo apt-get install openjdk-8-jdk
# 查看java版本,看是否安装成功
java -version
-
下载zookeeper
下载地址 -
解压及拷贝zookeeper到指定目录
tar -zxvf zookeeper-3.3.3.tar.gz
mv zookeeper-3.3.3 /usr/local/zookeeper
- 设置及更新环境变量
export ZOOKEEPER_HOME=/usr/local/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
source /etc/profile
- 将conf文件夹下的zoo_sample.cfg拷贝一份为zoo.cfg、修改dataDir路径、在zookeeper文件夹下创建zkData文件夹
cp zoo_sample.cfg zoo.cfg
vim zoo.cfg
dataDir=/usr/local/zookeeper/zkData
- zookeeper常用命令
# bin目录下执行
# 启动zookeeper服务器
./zkServer.sh start
# 启动zookeerper客户端
./zkCli.sh
# 退出zookeeper客户端界面:
quit
# 终止zookeeper服务器提供服务
./zkServer.sh stop
# 查看zookeeper服务器状态
./zkServer.sh status
# 查看状态结果:
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Mode: standalone
- zoo.cfg初始配置文件参数解读
# zookeeper服务器和客户端心跳间隔时间(单位ms)
tickTime=2000
# leader和follower初始通信时限(单位tickTime)
# leader和follower之间的第一次通信超过initLimit*tickTime=10*2s=20s没有通信,就会被认为leader和follower连不上了.initLimit为心跳时间间隔次数
initLimit=10
# leader和follower同步通信时限(单位tickTime)
# 集群正常启动后,leader和follower之间的通信超过syncLimit*tickTime=5*2s=10s没有通信,就会被认为leader和follower连不上了
syncLimit=5
# 数据文件目录+数据持久化路径,主要用于保存zookeeper中的数据
dataDir=/usr/local/zookeeper/zkData
# 对客户端提供服务的端口
clientPort=2181
- zookeeper内部原理
- 选举机制
-
半数机制: 集群中半数以上的机器存活,集群可用.所以zookeeper适合安装奇数台服务器.
-
zookeeper工作时有一个节点为leader,其他为follower.leader是通过内部的选举机制临时产生的.
-
ZXID(参考链接):
- ZXID 由 Leader 节点生成,有新写入事件时,Leader 生成新 ZXID 并随提案一起广播,每个结点本地都保存了当前最近一次事务的 ZXID,ZXID 是递增的,所以谁的 ZXID 越大,就表示谁的数据是最新的。
- ZXID的生成规则如下:
- ZXID 由两部分组成:
任期:完成本次选举后,直到下次选举前,由同一 Leader 负责协调写入;
事务计数器:单调递增,每生效一次写入,计数器加一。 - ZXID 的低 32 位是计数器,所以同一任期内,ZXID 是连续的,每个结点又都保存着自身最新生效的 ZXID,通过对比新提案的 ZXID 与自身最新 ZXID 是否相差“1”,来保证事务严格按照顺序生效的。
-
全新集群选举
若进行Leader选举,则至少需要两台机器,这里选取3台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器Server1启动时,其单独无法进行和完成Leader选举,当第二台服务器Server2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入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服务器。
对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时Server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于Server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。 - 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader。
- 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。
-
非全新集群选举
对于运行正常的 zookeeper 集群,中途有机器 down 掉,需要重新选举时, 选举过程就需要加入数据 ID、服务器 ID 和逻辑时钟。
- 数据 ID:数据新的 version 就大,数据每次更新都会更新 version。
- 服务器 ID:就是我们配置的 myid 中的值,每个机器一个。
- 逻辑时钟:这个值从 0 开始递增,每次选举对应一个值。 如果在同一次选举中,这个值是一致的。 这样选举的标准就变成:
① 逻辑时钟小的选举结果被忽略,重新投票;
②统一逻辑时钟后,数据 id 大的胜出;
③数据 id 相同的情况下,服务器 id 大的胜出;
根据这个规则选出 leader。
-
- zookeeper节点类型
Zookeeper 中节点类型按持久化可分为临时节点和持久节点,按顺序性可分为顺序和无序。
- 持久节点:节点创建后,会一直存在,不会因客户端会话失效而删除
- 持久顺序节点:基本特性与持久节点一致,创建节点的过程中,zookeeper会在其名字后自动追加一个单调增长的数字后缀,作为新的节点名
- 临时节点:客户端会话失效或连接关闭后,该节点会被自动删除,且不能再临时节点下面创建子节点
应用场景: 服务器节点动态上下线 - 临时顺序节点:基本特性与临时节点一致,创建节点的过程中,zookeeper会在其名字后自动追加一个单调增长的数字后缀,作为新的节点名
- zookeeper分布式安装部署
- 集群规划
机器编号 | IP地址(使用的虚拟机) | 端口 |
---|---|---|
Zk-1 | 192.168.145.3 | 2181 |
Zk-2 | 192.168.145.3 | 2182 |
Zk-3 | 192.168.145.3 | 2183 |
- 新建集群文件夹并准备三个zookeeper
# /usr/local目录下执行
sudo mkdir zk-cluster
sudo cp -r /usr/local/zookeeper/ /usr/local/zk-cluster/zk1/
sudo cp -r /usr/local/zookeeper/ /usr/local/zk-cluster/zk2/
sudo cp -r /usr/local/zookeeper/ /usr/local/zk-cluster/zk3/
- 创建数据和日志文件夹,并在zkData文件夹下新建myid文件添加与server对应的编号,
# 在zk1、zk2、zk3目录下分别执行
sudo mkdir zkData(之前创建单机模式的时候创建过)
sudo mkdir log
# 在zk1的zkData下执行
sudo touch myid
sudo vim myid
输入1保存退出
# 在zk2的zkData下执行
sudo touch myid
sudo vim myid
输入2保存退出
# 在zk3的zkData下执行
sudo touch myid
sudo vim myid
输入3保存退出
- 修改conf/zoo.cfg配置文件
tickTime=2000
initLimit=10
syncLimit=5
# 下面三行配置zk2/zk3类似
dataDir=/usr/local/zk-cluster/zk1/zkData
dataLogDir=/usr/local/zk-cluster/zk1/log
# zk2为2182,zk3为2183
clientPort=2181
# 集群的配置文件(zk2和zk3相同)
#第几个服务器(1,2,3来自数据目录的一个myid文件,该文件里面保存着当前集群的标识(1,2,3))
# 后面的ip代表将绑定那个ip地址 第一个端口:代表在集群内部,数据复制的接口 第二个端口代表:选举端口
server.1=192.168.145.3:2888:3888
server.2=192.168.145.3:2889:3889
server.3=192.168.145.3:2887:3887
- 启动zk1、zk2、zk3
# 在zk-cluster目录下执行
./zk1/bin/zkServer.sh start
./zk2/bin/zkServer.sh start
./zk3/bin/zkServer.sh start
# 启动完成后可以通过以下命令查看服务器状态
./zk1/bin/zkServer.sh status (follower)
./zk2/bin/zkServer.sh status (leader)
./zk3/bin/zkServer.sh status (follower)
- zookeeper常用shell命令
-
help: 显示所有操作命令
-
ls /: 查看当前znode中所包含的内容
-
ls2 /: 查看当前znode详细数据
-
create /znode_name znode_value: 创建节点(持久型)
create /sanguo "shuguo" create /sanguo/shuguo "liubei"
-
get /znode_name: 获取节点的值
比如可以应用于服务器节点动态上下线.如果能获取到节点中的值,则说明服务器节点存在,否则不存在.[zk: localhost:2181(CONNECTED) 12] get /sanguo shuguo cZxid = 0x100000002 ctime = Wed Jan 06 18:41:01 ULAT 2021 mZxid = 0x100000002 mtime = Wed Jan 06 18:41:01 ULAT 2021 pZxid = 0x100000003 cversion = 1 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 6 numChildren = 1 [zk: localhost:2181(CONNECTED) 13] get /sanguo/shuguo liubei cZxid = 0x100000003 ctime = Wed Jan 06 18:41:38 ULAT 2021 mZxid = 0x100000003 mtime = Wed Jan 06 18:41:38 ULAT 2021 pZxid = 0x100000003 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 6 numChildren = 0
-
create -e /znode_name znode_value: 创建短暂节点
[zk: localhost:2181(CONNECTED) 14] create -e /sanguo/weiguo "caocao" Created /sanguo/weiguo [zk: localhost:2181(CONNECTED) 17] ls /sanguo [shuguo, weiguo] [zk: localhost:2181(CONNECTED) 18] quit zhaolijian@ubuntu:/usr/local/zk-cluster/zk1/bin$ sudo ./zkServer.sh start zhaolijian@ubuntu:/usr/local/zk-cluster/zk1/bin$ ./zkCli.sh [zk: localhost:2181(CONNECTED) 0] ls /sanguo [shuguo] # 可以看到没有weiguo了,因为它是短暂节点
-
create -s /znode_name znode_value: 创建带序号的节点(持久型)
[zk: localhost:2181(CONNECTED) 1] create -s /sanguo/wuguo "sunquan" Created /sanguo/wuguo0000000002 [zk: localhost:2181(CONNECTED) 2] ls /sanguo [wuguo0000000002, shuguo]
-
set /znode_name znode_value: 修改节点数据值
[zk: localhost:2181(CONNECTED) 3] get /sanguo/shuguo liubei [zk: localhost:2181(CONNECTED) 4] set /sanguo/shuguo "diaochan" [zk: localhost:2181(CONNECTED) 6] get /sanguo/shuguo diaochan
-
get /znode_name watch: 监听节点值变化(只对一次修改有效,再次set就不能监听了)
# zk1 [zk: localhost:2181(CONNECTED) 8] get /sanguo/shuguo watch diaochan # 启动zk2 client [zk: localhost:2181(CONNECTED) 0] set /sanguo/shuguo "wode" # zk1: [zk: localhost:2181(CONNECTED) 9] WATCHER:: WatchedEvent state:SyncConnected type:NodeDataChanged path:/sanguo/shuguo
-
ls /zparent_node watch: 监听节点的子节点变化(路径变化,只一次有效)
# zk1: 监听/sanguo节点下的子节点 [zk: localhost:2181(CONNECTED) 10] ls /sanguo watch [wuguo0000000002, shuguo] # zk2: 在/sanguo节点下增加节点 [zk: localhost:2181(CONNECTED) 4] create /sanguo/weiguo "caopei" Created /sanguo/weiguo # zk1: [zk: localhost:2181(CONNECTED) 11] WATCHER:: WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/sanguo
-
delete /znode: 删除节点
[zk: localhost:2181(CONNECTED) 11] ls /sanguo [wuguo0000000002, shuguo, weiguo] [zk: localhost:2181(CONNECTED) 12] delete /sanguo/weiguo [zk: localhost:2181(CONNECTED) 13] ls /sanguo [wuguo0000000002, shuguo]
-
rmr /znode: 递归删除节点
[zk: localhost:2181(CONNECTED) 14] rmr /sanguo [zk: localhost:2181(CONNECTED) 15] ls / [zookeeper]
-
stat /znode: 查看节点状态
[zk: localhost:2181(CONNECTED) 18] create /sanguo "tianzi" Created /sanguo [zk: localhost:2181(CONNECTED) 19] stat /sanguo cZxid = 0x100000015 ctime = Wed Jan 06 20:07:02 ULAT 2021 mZxid = 0x100000015 mtime = Wed Jan 06 20:07:02 ULAT 2021 pZxid = 0x100000015 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 6 numChildren = 0
- stat查看节点状态结构体参数
- cZxid: 创建节点的事务zxid
每次修改zookeeper状态都会收到一个zxid形式的时间戳,也就是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子节点数量
- zookeeper监听器原理
- zookeeper写数据流程
- 服务器动态上下线案例分析
注:
- 服务器1/2/3不是zookeeper服务器,而是为客户端1/2/3提供服务的服务器.
- zookeeper集群中有leader/follower服务器,图中没有展示
- zookeeper集群框中的80 nodes / 90 nodes /95 nodes是连接到服务器的客户端数量