Zookeeper
1. Zookeeper
1.1 Zookeeper概述
Zookeeper
是一个分布式协调服务
的开源框架。主要用来解决分布式集群中应用系统的一致性问题,例如怎样避免同时操作同一数据造成脏读的问题。
ZooKeeper 本质上是一个分布式的小文件存储系统
。
诸如:统一命名服务、分布式配置管理、分布式消息队列、分布式锁、分布式协调等功能。
1.2 Zookeeper特性
全局数据一致
: 集群中数据一致,无论client连接在哪个服务器上数据也是一致的- 可靠性 : 即消息被一台服务器接受,所有服务器都接受
- 顺序性
- 全局有序 : 如在一台服务器上a在b之前发布 , 所有Server上a都在b之前
- 偏序: 如b在a后被同一个发布者发布 , a必在b之前
- 数据更新原子性 : 要么更新成功, 要么失败 , 不存在中间状态
- 实时性 : zookeeper保证客户端在一个时间间隔内获得服务器的更新信息或失效信息
- zookeeper本质
- 存储系统:存储东西 目录树
- 小文件:存储东西有限制
- 分布式:多台机器
- 分布式、集群:共同点指的是多台机器组成
1.3 Zookeeper集群角色
Leader
:
Zookeeper 集群工作的核心
事务请求
(写操作)的唯一调度和处理者,保证集群事务处理的顺序性;
集群内部各个服务器的调度者。
对于 create,setData,delete 等有写操作的请求,则需要统一转发给
leader 处理,leader 需要决定编号、执行操作,这个过程称为一个事务。
Follower
:
处理客户端非事务
(读操作)请求,转发事务请求给 Leader;
参与集群 Leader 选举投票
。
此外,针对访问量比较大的 zookeeper 集群,还可新增观察者角色。
Observer
:
观察者角色,观察 Zookeeper 集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给 Leader服务器进行处理。
不会参与任何形式的投票只提供非事务服务
,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。
- 主从集群 主备集群
主从集群:
主角色 leader master 大哥
从角色 follower slave 小弟
主从角色各司其职,从角色要收到主角色的管理。(常见的是一主多从)
主备集群:
主角色 active
备角色 standby
主备集群解决了单点故障问题。形成了高可用。
主备集群中同一时间,有且只有一个是主角色。当主角色挂了,备角色切换成为主角色,保证服务的连续。
zk集群是一个标准的主从集群。
1.4 ZooKeeper ZooKeeper 集群搭建
注意事项 :
- 提前安装 jdk
- 检测集群时间是否同步
- 检测防火墙是否关闭
- 检测主机 ip映射有没有配置
- 修改 ZooKeeper 配置文件
- 远程复制分发安装文件
- 设置 myid
- 启动 ZooKeeper 集群
详细安装可以看笔记<<Zookeeper集群的搭建>>\
2. ZooKeeper 数据模型
标准文件系统
(windows linux)- 文件系统目录树是以/根目录开始的
- 根目录下可以创建文件和文件夹 但是只有文件夹下可以继续创建下一级子目录
- 数据是保存在文件中 文件夹往往以路径的形式存在
- path路径的唯一性
- 相同类型的文件不能够重名
ZooKeeper 的数据模型,在结构上和标准文件系统的非常相似,拥有一个层次的命名空间,都是采用树形层次结构
,ZooKeeper 树中的每个节点
被称为—Znode
。和文件系统的目录树一样,ZooKeeper 树中的每个节点可以拥有子节点。
不同之处 :
- Znode 兼具文件和目录两种特点。
- Znode 具有原子性操作
- Znode 存储数据大小有限制 ,
通常以 KB 为大小单位 , 大小至多 1M
- Znode 通过路径引用 ,
路径必须是绝对的 , 必须由斜杠字符来开头
2.1 数据结构图
图中的每个节点称为一个 Znode。 每个 Znode 由 3 部分组成:
① stat
:此为状态信息, 描述该 Znode 的版本, 权限等信息
② data
:与该 Znode 关联的数据
③ children
:该 Znode 下的子节点
在中文中节点有两层含义
- 节点树目录上的每个znode
- 节点指zookeeper集群中的每个server服务器
2.2 节点类型
Znode 有两种,分别为临时节点
和永久节点
。
节点的类型在创建时即被确定,并且不能改变。
临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然可以也可以手动删除。临时节点不允许拥有子节点。
永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
Znode 还有一个序列化
的特性,如果创建的时候指定的话,该 Znode 的名字后面会自动追加一个不断增加的序列号 , 可以记录每个子节点创建的先后顺序
。它的格式为“%10d”(10 位数字,没有数值的数位用 0 补充,例如“0000000001”)。
序列化节点可以创建两个名字相同的节点 , 会在相同的节点名字后自动追加一个不断增加的序列号 , 而非序列化节点不能创建同名的节点 .
zookeeper中的znode类型 : 4种
- EPHEMERAL_SEQUENTIAL : 临时序列化
- PERSISTENT_SEQUENTIAL : 永久序列化
- EPHEMERAL : 临时非序列化
- PERSISTENT : 永久非序列化
2.3 节点属性
每个 znode 都包含了一系列的属性,通过命令 get,可以获得节点的属性。
dataVersion:数据版本号,每次对节点进行 set 操作,dataVersion 的值都会增加 1(即使设置的是相同的数据),可有效避免了数据更新时出现的先后顺序问题。
cversion :子节点的版本号。当 znode 的子节点有变化时,cversion 的值就会增加 1。
aclVersion :ACL 的版本号。
cZxid :Znode 创建的事务 id。
mZxid :Znode 被修改的事务 id,即每次对 znode 的修改都会更新 mZxid。对于 zk 来说,每次的变化都会产生一个唯一的事务 id,zxid(ZooKeeperTransaction 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
3.1 客户端连接
运行 zkCli.sh –server ip
(自己zookeeper集群的某一个ip , 比如以博主本人为例./zkCli.sh -server node-1
) 进入命令行工具。退出的命令是quit
3.2 shell 基本操作
创建节点
create [-s] [-e] path data acl
-s
或**-e** 分别指定节点特性,顺序
或临时节点,若不指定,则表示持久节点;acl 用来进行权限控制. path表示创建节点放的位置 , data指创建的数据.
创建顺序节点: create -s /test 123
==> 表示创建了在test目录下数据为123的持久顺序节点
创建临时节点:create -e /test1 123
==>表示创建了在test1目录下数据为123的临时非顺序节点
读取节点
与读取相关的命令有 ls
命令和get
命令,ls 命令可以列出 Zookeeper 指定节点下的所有子节点,只能查看指定节点下的第一级的所有子节点;get 命令可以获取 Zookeeper 指定节点的数据内容和属性信息。
ls path [watch]
get path [watch]
ls2 path [watch]
更新节点
set path data [version]
data 就是要更新的新内容,version 表示数据版本。
现在 dataVersion 已经变为 1 了,表示进行了更新。
删除节点
delete path [version]
若删除节点存在子节点,那么无法删除该节点,必须先删除子节点,再删除父节点。
Rmr path
可以递归删除节点
quota
setquota -n|-b val path
对节点增加限制。
n
:表示子节点的最大个数
b
:表示数据值的最大长度
val
:子节点最大个数或数据值的最大长度
path
:节点路径
listquota path
列出指定节点的 quota
子节点个数为 2,数据长度-1 表示没限制
delquota [-n|-b] path
删除 quota
tips : quota限制 是软性限制 如果超过并不会阻止,只会在日志中默默的警告的一下。
实际中可通过针对日志的监控来判断是否超出限制。
其他命令
history : 列出命令历史
redo: 该命令可以重新执行指定命令编号的历史命令,命令编号可以通过history 查看
补充知识点 : Zookeeper的stat结构
ZooKeeper命名空间中的每个znode都有一个与之关联的stat结构。 znode的stat结构中的字段显示如下,各自的含义如下:
- cZxid:这是导致创建znode更改的事务ID。
- mZxid:这是最后修改znode更改的事务ID。
- pZxid:这是用于添加或删除子节点的znode更改的事务ID。
- ctime:表示从1970-01-01T00:00:00Z开始以毫秒为单位的znode创建时间。
- mtime:表示从1970-01-01T00:00:00Z开始以毫秒为单位的znode最近修改时间。
- dataVersion:表示对该znode的数据所做的更改次数。
- cversion:这表示对此znode的子节点进行的更改次数。
- aclVersion:表示对此znode的ACL进行更改的次数。
- ephemeralOwner:如果znode是ephemeral类型节点,则这是znode所有者的 session ID。 如果znode不是ephemeral节点,则该字段设置为零。
- dataLength:这是znode数据字段的长度。
- numChildren:这表示znode的子节点的数量。
在ZooKeeper Java shell中,可以使用stat
或ls2
命令查看znode的stat结构。 具体说明如下:
- 使用
stat
命令查看znode的stat
结构:
[zk: localhost(CONNECTED) 0] stat /zookeeper
cZxid = 0x0
ctime = Thu Jan 01 05:30:00 IST 1970
mZxid = 0x0
mtime = Thu Jan 01 05:30:00 IST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
- 使用
ls2
命令查看znode的stat结构:
[zk: localhost(CONNECTED) 1] ls2 /zookeeper
[quota]
cZxid = 0x0
ctime = Thu Jan 01 05:30:00 IST 1970
mZxid = 0x0
mtime = Thu Jan 01 05:30:00 IST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
4. Zookeeper Watcher
-
监听步骤
- 第一步 设置监听(比如班主任告诉班长监视谁玩手机)
- 第二步 执行监听(班长一直看着谁在玩手机 只对设置监听事件感兴趣)
- 第三步 监听触发 (班长发现有人玩手机 把监听的事件结果回调通知给班主任)
-
如果zk中,客户端对zk目录数上的节点变化产生了兴趣,如何监听?
例如:就对某个节点删除感兴趣
- 首先客户端在zk上设置监听 帮我看着某个节点是否删除
- 其次zk集群监控该节点的变化
- 最后当该节点删除 监听触发 告诉设置监听的客户端
-
shell操作watcher
- get /allen watch 设置监听(监听节点数据是否改变)
- set /allen 778899 触发监听
- WatchedEvent state:SyncConnected type:NodeDataChanged path:/allen 监听回调通知
ZooKeeper 提供了分布式数据发布/订阅功能 , Watcher 机制
来实现这种分布式的通知功能
总的来说可以概括 Watcher 为以下三个过程:客户端向服务端注册Watcher、服务端事件发生触发 Watcher、客户端回调 Watcher 得到触发事件情况
4.1 Watch 机制特点
- 先注册再触发
- 一次性触发
- 事件封装
- event 异步发送
4.2 Shell 客户端设置 watcher
设置节点数据变动监听 : get /itcast watch
通过另一个客户端更改节点数据 : set /itcast hello
此时设置监听的节点收到通知:
4.3 通知状态和事件类型
其中连接状态事件
(type=None, path=null
)不需要客户端注册,客户端只要有需要直接处理就行了。
- zk中监听分为两大类
- 连接状态事件监听 自动存在 不需要用户注册 客户端需要就处理 不需要就忽略不计
- 自定义监听 先注册再监听 一次性监听
5. ZooKeeper Java API
Zookeeper
是在 Java 中客户端主类,负责建立与 zookeeper 集群的会话,并提供方法进行操作。
Watcher
接口表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包含 KeeperState 和 EventType 两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法:process
(WatchedEvent event)。
5.1 基本使用
建立 java maven 项目,引入 maven pom 坐标。
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
里面有实现持久监听的代码
package cn.itcast.zk.client;
import org.apache.zookeeper.*;
/**
* Created by Allen Woon
*/
public class TestZkClient {
static ZooKeeper zk = null;
public static void main(String[] args) throws Exception{
//通过zookeeper创建java客户端
//参数1:zk集群地址,host:port 多个以英文逗号分隔
//参数2:会话超时时间 集群默认 30s
//参数3: 监听实现
zk = new ZooKeeper("node-1:2181,node-2:2181", 30000, new Watcher() {
//该方法就是监听触发回调客户端的方法 如果有需求 就可以这里进行相关处理
//3、监听回调的地方
public void process(WatchedEvent event) {
System.out.println("连接状态为:"+event.getState());
System.out.println("事件发生的路劲为:"+event.getPath());
System.out.println("事件发生的类型:"+event.getType());
//要想实现永久监听,就在上一个监听结束之前 继续设置同类型监听
// try {
// zk.getData("/itcastbyjava",true,null);
// } catch (KeeperException e) {
// e.printStackTrace();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
});
// zk.create("/itcastbyjava2","么么哒".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 1、设置监听 监听节点的数据是否发生改变
// zk.getData("/itcastbyjava",true,null);
// //2、触发监听
// zk.setData("/itcastbyjava","呵呵哒3".getBytes(),-1);
// zk.setData("/itcastbyjava","呵呵哒4".getBytes(),-1);
// zk.delete("/itcastbyjava2",-1);
//是否存在
zk.exists("/itcastbyjava",true);
zk.delete("/itcastbyjava",-1);
zk.close();
}
}
5.2 更多操作演示
public static void main(String[] args) throws Exception {
// 初始化 ZooKeeper 实例(zk 地址、会话超时时间,与系统默认一致、watcher)
ZooKeeper zk = new ZooKeeper("node-21:2181,node-22:2181", 30000, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("事件类型为:" + event.getType());
System.out.println("事件发生的路径:" + event.getPath());
System.out.println("通知状态为:" +event.getState());
}
});
// 创建一个目录节点
zk.create("/testRootPath", "testRootData".getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
// 创建一个子目录节点
zk.create("/testRootPath/testChildPathOne", "testChildDataOne".getBytes(),
Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
System.out.println(new String(zk.getData("/testRootPath",false,null)));
// 取出子目录节点列表
System.out.println(zk.getChildren("/testRootPath",true));
// 修改子目录节点数据
zk.setData("/testRootPath/testChildPathOne","modifyChildDataOne".getBytes(),-1);
System.out.println("目录节点状态:["+zk.exists("/testRootPath",true)+"]");
// 创建另外一个子目录节点
zk.create("/testRootPath/testChildPathTwo", "testChildDataTwo".getBytes(),
Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
System.out.println(new String(zk.getData("/testRootPath/testChildPathTwo",true,null)));
// 删除子目录节点
zk.delete("/testRootPath/testChildPathTwo",-1);
zk.delete("/testRootPath/testChildPathOne",-1);
// 删除父目录节点
zk.delete("/testRootPath",-1);
zk.close();
}
6. ZooKeeper 选举机制
zookeeper 默认的算法是 FastLeaderElection,采用投票数大于半数则胜出的逻辑。
6.1 概念
服务器 ID
比如有三台服务器,编号分别是 1,2,3。
编号越大在选择算法中的权重越大。
选举状态
LOOKING,竞选状态。
FOLLOWING,随从状态,同步 leader 状态,参与投票。
OBSERVING,观察状态,同步 leader 状态,不参与投票。
LEADING,领导者状态。
数据 ID
服务器中存放的最新数据 version。
值越大说明数据越新,在选举算法中数据越新权重越大。
逻辑时钟
也叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票 这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。
6.2 全新集群选举
假设目前有 5 台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下:
- 服务器 1 启动,给自己投票,然后发投票信息,由于其它机器还没有启
动所以它收不到反馈信息,服务器 1 的状态一直属于 Looking。 - 服务器 2 启动,给自己投票,同时与之前启动的服务器 1 交换结果,由
于服务器 2 的编号大所以服务器 2 胜出,但此时投票数没有大于半数,
所以两个服务器的状态依然是 LOOKING。 - 服务器 3 启动,给自己投票,同时与之前启动的服务器 1,2 交换信息,
由于服务器 3 的编号最大所以服务器 3 胜出,此时投票数正好大于半数,
所以服务器 3 成为领导者,服务器 1,2 成为小弟。 - 服务器 4 启动,给自己投票,同时与之前启动的服务器 1,2,3 交换信息,
尽管服务器 4 的编号大,但之前服务器 3 已经胜出,所以服务器 4 只能
成为小弟。 - 服务器 5 启动,后面的逻辑同服务器 4 成为小弟。
6.3 非全新集群选举
对于运行正常的 zookeeper 集群,中途有机器 down 掉,需要重新选举时,选举过程就需要加入数据 ID、服务器 ID 和逻辑时钟。
数据 ID:数据新的 version 就大,数据每次更新都会更新 version。
服务器 ID:就是我们配置的 myid 中的值,每个机器一个。
逻辑时钟:这个值从 0 开始递增,每次选举对应一个值。 如果在同一次选举中,这个值是一致的。
这样选举的标准就变成:
1、逻辑时钟小的选举结果被忽略,重新投票;
2、统一逻辑时钟后,数据 id 大的胜出;
3、数据 id 相同的情况下,服务器 id 大的胜出;
根据这个规则选出 leader。
7. Zookeeper 典型应用
7.1 数据发布与订阅(配置中心)
发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到 ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新
。
注意:适合数据量很小
的场景,这样数据更新可能会比较快。
7.2 命名服务(Naming Service)
在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息
。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等——这些我们都可以统称他们为名字(Name)
通过调用 ZK 提供的创建节点的 API,能够很容易创建一个全局唯一的 path
,这个 path 就可以作为一个名称。
阿里巴巴集团开源的分布式服务框架 Dubbo 中使用 ZooKeeper 来作为其命名服务,维护全局的服务地址列表。
7.3 分布式锁
分布式锁这个主要得益于 ZooKeeper 保证了数据的强一致性。锁服务可以分为两类,一个是保持独占
,另一个是控制时序
。
保持独占
,就是所有试图来获取这个锁的客户端,最终只有一个
可以成功获得这把锁。
控制时序
,就是所有试图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。
扩展 : zk给其他软件选举
选举老大 ---- 意味着找出唯一性
- 集群中的每个机器的模块首先都去zk集群指定的目录下创建znode节点(/itcast/master)非序列化 , 短暂
- 谁创建成功该节点 , 谁对应的那个机器上的角色就是老大. 创建失败的机器在此设置一个监听 , 监听该节点是否存在
zk.exits("/itcast/master",true)
- 当老大角色出现故障 , 断开跟zk集群的连接—> 节点消失 —> 触发监听 —> 回调通知监听的小弟 —> 各个小弟里面启动连接去创建节点(/itcast/master) , 谁成功谁就是新的老大 , 没有成功的继续设置监听
- 之前老大抢救回来 , 首先去抢注该节点 , 如果节点已经存在 , 设置监听 , 返回做小弟
这里设置的zk node特性 : 短暂性 非序列化 监听机制