视频教程传送门 -> https://www.bilibili.com/video/BV19b411772h
1. Zookeeper
Zookeeper是一个分布式协调服务的开源框架。
Zookeeper本质是一个分布式的小文件存储系统。
@Zookeeper特性
1)全局数据一致:每个服务器保存一份相同的数据副本,客户端无论连接哪个服务器展示的数据是一致的
2)可靠性:消息(即增删改查)被一台服务器接受,那么也将被所有服务器接受
3)顺序性:全局有序 -- 在一台服务器上,如果消息a在消息b前发布,则在所有服务器上都是如此
偏序 -- 消息b在消息a后被同一个发送着发布,a必将排在b前面
4)数据更新原子性:一次数据更新要么成功(半数以上节点成功),要么失败
5)实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息
ZooKeeper具备CP特性
任何时刻的访问请求能得到一致的数据结果
系统对网络分割具备容错性
不保证每次服务请求的可用性
@Zookeeper集群角色
Leader
Zookeeper集群工作的核心
事务请求(写操作)的唯一调度和处理者,保证集群事务处理的顺序性
集群内部各个服务器的调度者
说明:对于create、setData、delete等有写操作的请求,需要统一转发给Leader处理,Leader需要决定编号、执行操作,这个过程成为一个事务。
Follower
处理客户端非事务(读操作)请求,转发事务请求给Leader
参与集群Leader选举投票
Observer 针对访问量比较大的Zookeeper集群,可以增加观察者角色(横向扩展)
提供非事务服务、不参加投票
@Zookeeper集群搭建
Zookeeper集群通常由2n+1台服务器组成(Leader选举基于Paxos算法实现)
1)Leader+Follower模式
- 配置主机名称和IP地址映射
- 修改Zookeeper配置文件
- 远程复制分发安装文件
- 设置myid
- 启动Zookeeper集群
2)启用Observer模式
可在对应节点的配置文件添加 peerType=observer
并且在配置文件指定哪些节点被指定为Observer,如
server.1:localhost:2181:3181:observer
以3台服务器为例,搭建步骤如下
step1
确认环境已安装jdk
检查集群时间是否同步
检查防火墙是否关闭(生产环境配置防火墙规则)
检查是否配置主机IP映射
step2
tar -xzvf zookeeper-xxx.tar.gz
mv zookeeper-xxx zookeeper
step3
修改环境变量(3台Zookeeper都要修改)
vi /etc/profile 添加如下行
export ZOOKEEPER_HOME=/home/hadoop/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
source /etc/profile
step4
修改Zookeeper配置文件(先在一台修改)
cd zookeeper/conf
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg 添加如下行
#可以修改数据路径
dataDir=/root/apps/zookeeper/zkdata
#两个端口分别是 心跳端口、选举端口
server.1=node-1:2888:3888
server.2=node-2:2888:3888
server.3=node-3:2888:3888
创建文件myid
cd /root/apps/zookeeper/zkdata
echo 1 > myid
step5
分发安装包到其它服务器
scp -r /root/apps root@node-1:/
scp -r /root/apps root@node-2:/
step6
修改其它服务器的myid文件
服务器node-1 修改myid内容为2
服务器node-2 修改myid内容为3
step7
在每台服务器启动zookeeper
cd bin
./zkServer.sh start
查看状态(还可以看到是leader还是follower)
./zkServer.sh status
2. Zookeeper数据模型
@Znode特点
类似于文件系统的目录树,但也有的不同之处,其特点如下
1)Znode兼具文件和目录两种特点
- 既像文件一样维护数据、元信息、ACL、时间戳等数据结构
- 又像目录一样可以作为路径标识的一部分,且可以具有子Znode
用户可以对Znode具有增删改查
2)Znode具有原子性操作
- 读操作将获取与节点相关的所有数据
- 写操作将替换掉节点的所有数据
每一个节点都拥有自己的ACL(访问控制列表),这个列表限定了特定用户对目标节点可以执行的操作
3)Znode存储数据大小有限制
Zookeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储
相反,它用来管理调度数据,如分布式应用中的配置文件信息、状态信息、汇集位置等(都是很小的数据,KB级)
Zookeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M
4)Znode通过路径引用
路径必须是绝对路径、且是唯一的
@Znode组成
每个Znode由3部分组成
stat 状态信息,描述Znode版本、权限等信息
data 与该Znode关联的数据
children 该Znode下的子节点
@Znode节点类型
临时节点&永久节点
临时节点:该节点的生命周期依赖于创建它们的会话
会话结束临时节点将被自动删除,也可以手动删除
临时节点不允许拥有子节点
永久节点:该节点的生命周期不依赖于会话,执行删除操作才会被删除
Znode的序列特性
如果创建的时候指定的话,该Znode的名字后面会自动追加一个不断增加的序列号
序列号记录每个子节点创建的先后顺序,对于此节点的父节点来说是唯一的
序列号的格式为"%10d"(10位数字,没有数值的数位用0补充)
@Znode属性
通过命令get,可以获得节点的属性
dataVersion:数据版本号,每次对节点进行set操作,dataVersion的值都会增加1(即使设置的是相同数据)
可以有效避免数据更新时出现的先后顺序问题
cversion:子节点的版本号,当Znode的子节点发生变化时,cversion的值就会加1
aclVersion:ACL的版本号
cZxid:Znode创建的事务id
mZxid:Znode被修改的事务id,每次对Znode的修改都会更新该值
对于zk来说,每次的变化都会产生一个唯一的事务id,zxid(Zookeeper Transaction Id)
通过zxid可以确定更新操作的先后顺序。例如,如果zxid1小于zxid2,说明zxid1操作先于zxid2发生
zxid对于整个zk都是唯一的,即使操作的是不同的znode
ctime:节点创建时的时间戳
mtime:节点最新一次更新发生时的时间戳
ephemeralOwner:如果该节点位临时节点,ephemeralOwner值表示与该节点绑定的session id;反之,ephemeralOwner为0
在client和server通信之前,首先需要建立连接(session),连接建立后,如果发生连接超时、授权失败、显式关闭连接,连接处于CLOSED状态,此时session结束
3. Zookeeper shell
@客户端连接
运行 zkCli.sh -server ip 进入命令行工具
输入help,输出zk shell提示
说明:-server可选,不带会在本机查找zk,带上连接到远端zk
@shell基本操作
1)创建节点
create [-s] [-e] path data acl
-s指定创建顺序节点
-e指定创建临时节点
acl用来进行权限控制
2)读取节点
ls 命令 列出Zookeeper指定节点下的所有子节点(只能查看第一级子节点)
get 命令 获取指定Znode的数据内容和属性信息
ls2 命令 列出的信息比ls详细,包括子节点和属性信息,但不包括数据内容
ls path [watch]
get path [watch]
ls2 path [watch]
3)更新节点
set path data [version]
data 更新的新内容
version 数据的版本
更新后dataVersion会递增
4)删除节点
delete path [version]
若删除的节点存在子节点则无法删除
可以先删除子节点
或者使用 rmr path 递归删除节点
5)quata
setquota -n|-b val path 对节点增加限制
-n 表示子节点的最大个数
-b 表示数据值的最大长度
val 子节点的最大个数或数据值的最大长度
path 节点路径
listquota path 列出指定节点的quota
delquota [-n|-b] path 删除quota
注意:即使节点数超了,仍能创建,会在日志zookeeper.out中打印WARN
6)history列出命令历史
redo重新执行指定命令编号的历史命令
4. Zookeeper Watcher
Zookeeper提供了分布式数据发布/订阅功能
一对多订阅=> 能让多个订阅者同时监听某一个主题对象
当主题对象自身状态变化时,会通知所有订阅者
Zookeeper引入Watcher机制来实现分布式通知功能
Zookeeper允许客户端向服务器注册一个Watcher监听,当服务端的一些事件触发了这个Watcher,就会向指定的客户端发送一个事件来通知
触发事件的种类有节点创建、节点删除、节点改变、子节点改变等
@Watcher机制过程
客户端向服务端注册Watcher
服务端事件发生触发Watcher
客户端回调Watcher得到触发事件情况
@Watcher机制特点
1)一次性触发
事件发生触发监听,一个watcher event就会发送到设置监听的客户端
这种效果式一次性的,再发生同样的事件不会触发
2)事件封装
Zookeeper使用WatchedEvent对象来封装服务端事件并传递
WatchedEvent包含了每一个事件的三个基本属性:
通知状态(keeperState)
事件类型(eventType)
节点路径(path)
3)event异步发送
Watcher的通知事件从服务端发送到客户端是异步的
4)先注册再触发
Zookeeper中的Watcher机制必须客户端先去服务端注册监听
同一个事件类型在不同的通知状态中代表的含义不同,举例如下表
其中连接状态事件(type=None,path=null)不需要客户端注册,客户端只要有需要直接处理就行了
@Shell客户端设置Watcher
设置节点数据变动监听
【例】get /aaa0000000001 watch
通过另一个客户端更改节点数据
set /aaa0000000001 456789
此时设置监听的节点收到的通知
再次通过另一个客户端更改节点数据,设置监听的节点不会收到通知
5. Zookeeper选举机制
默认算法是FastLeaderElection,采用投票数大于半数则胜出的逻辑。
@相关概念
服务器ID
例如有三台服务器,编号分别为1、2、3
编号越大在选举算法中的权重越大
选举状态
LOOKING 竞选状态
FOLLOWING 随从状态,同步leader状态,参与投票
OBSERVING 观察状态,同步leader状态,不参与投票
LEADING 领导者状态
数据ID
服务器中存放的最新数据version
值越大说明数据越新,在选举算法中数据越新权重越大
逻辑时钟/投票次数
同一轮投票过程中的逻辑时钟值是相同的
每投完一次票这个数据会增加
@全新集群选举
假设目前有5台服务器,每台服务器均没有数据
编号分别是1、2、3、4、5,按编号依次启动
规则:每个机器都给自己投票、投票数过半选举结束
step1 服务器1启动,给自己投票
然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息
服务器1处于Looking状态
step2 服务器2启动,给自己投票
同时与服务器1交换信息
由于服务器2的编号大所以胜出
但此时投票数没有大于半数,服务器1、2处于Looking状态
step3 服务器3启动,给自己投票
同时与服务器1、2交换信息
由于服务器2的编号大所以胜出
此时投票数正好大于半数,投票结束,服务器3成为Leader,服务器1、2成为Follower
step4 服务器4启动,给自己投票
同时与服务器1、2、3交换信息
投票已结束,服务器4成为Follower
step5 服务器5启动,同服务器4
全新集群选举主要影响因素 -> 服务器编号
@非全新集群选举
对于运行正常的zookeeper集群,中途有服务器down,需要重新选举时,选举过程就需要加入数据ID、服务器ID和逻辑时钟
数据ID:数据新的version就大,数据每次更新都会更新version
服务器ID:配置的myid中的值,每个机器一个
逻辑时钟:这个值从0开始递增,每次选举对应一个值。如果在同一次选举中,这个值是一致的
选举Leader标准如下
1)逻辑时钟小的选举结果被忽略,重新投票
2)同一逻辑时钟后,数据id大的胜出
3)数据id相同的情况下,服务器id大的胜出
6. 典型应用
@数据发布与订阅(配置中心)
配置中心=> 发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新
应用在启动的时候会主动来获取一次配置,同时,在节点上注册一个Watcher
配置有更新会实时通知到订阅的客户端,从而达到获取最新配置信息的目的
例如,分布式搜索服务中,索引的元信息和服务器集群节点状态存放在ZK的一些指定节点,供各个客户端订阅使用
注意:适合数据量很小的场景,这样数据更新会比较快
@命名服务(Naming Service)
在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息
被命名的实体通常可以是集群中的机器、提供的服务地址、远程对象等 => 可以统称为名字(Name)
通过调用ZK提供的创建节点的API,可以创建一个全局唯一的path => 可以作为一个名称
例如,阿里巴巴开源的分布式服务框架Dubbo中使用Zookeeper来作为其命名服务,维护全局的服务地址列表
@分布式锁
锁服务可以分为两类,一个是保持独占,另一个是控制时序
1)保持独占
所有试图获取这个锁的客户端,最终只有一个可以成功获得这把锁
通常做法是把zk上的一个Znode看作一把锁,通过create Znode的方式来实现
所有客户端都去创建/distribute_lock节点(是临时节点且非序列化),最终成功创建的那个客户端拥有这把锁
2)控制时序
所有视图来获取这个锁的客户端,最终都会被安排执行,只是有个全局时序
做法基本和前述相同,只是/distribute_lock已经预先存在
客户端在它下面创建临时有序节点
父节点/distribute_lock维持一份sequence,保证子节点创建的时序性
推荐阅读:
我们能用zookeeper做什么 https://blog.csdn.net/zhangzq86/article/details/80981234