目录
简介
zookeeper是一个为分布式
提供一致性协调
服务的开源组件,使用java语言编写,主要是解决分布式条件的数据一致性问题。
典型的应用场景有分布式配置中心
、分布式注册中心
、分布式锁
、分布式队列
、集群选举
、分布式屏障
、发布/订阅
等场景。
zookeeper的本质是一个具有监听数据
变化进行并推送功能的小型文件管理
系统,运行在内存中:集群中各节点可以作为zookeeper的客户端,将共享的数据放在zookeeper中的某个目录(ZNode),由于目录具有层级结构且不同名
可以使得各个集群建数据互不干扰(类似于redis的key中放value,但zookeeper是目录下存放的是数据
或子目录
),这样集群中有任意节点对目录下的数据进行修改,其他节点通过监听机制就会立即发现,及时获取数据。(类似于可以给每个集群服务间创建 一个带有更新推送功能的共享文件夹
)
zookeeper的核心原则:半数以上
,此原则应用在集群选举(集群部署)、消息同步确认
特点:
例:配置中心
例:分布式注册中心
zk的数据持久化机制
zk也是基于内存存储的,要使得数据不会断电即失,需要在磁盘进行备份
zk在持久化上,与redis很相似。同样具有与RDB和AOF的类似功能。
-
事务日志:记录每一个事务操作,是一个二进制文件,只要创建出来就是
64M
(可通过preAllocSize
配置)。存放在dataLogDir
指定的位置,如果没有指定就和快照一起放入dataDir
指定的目录- 集群启动并与客户端连接后产生第一个事务,会触发创建第一个日志文件,当日志数量到达10w,将此刻数据
拍摄快照
然后会从下一个事务开始即第100001
个,创建一个新的日志文件,名字为第100001
个事务的id,继续循环以上操作
- 集群启动并与客户端连接后产生第一个事务,会触发创建第一个日志文件,当日志数量到达10w,将此刻数据
-
数据快照:用于保存
某一时刻
的数据- 触发机制:当事务日志次数 距离
上次拍摄快照
或启动时
已增加达到10w
次(默认数值,可以在配置文件中对snapCount
指定阀值)
但这个10w不是准确值,zk为了防止集群环境同时写入日志,通常会次数在(snapCount/2+1,snapCount)
区间内随机触发,使用的是子线程
- 触发机制:当事务日志次数 距离
与redis不同的是,这两个是默认同时开启的,且每一个
事务操作都会被写入事务日志
。当恢复时,zk先读取数据快照
进行全量恢复至拍摄此快照的那一时刻,然后再读取那次快照以后的日志文件
,在那一时刻的基础上进行增量恢复。
数据结构:
Zookeeper是一个类似于Unix文件系统的数据结构 ,一个大的文件夹(/
)里面包含着一些小的文件夹,每个小的文件夹中还可以包含着一些更小的文件夹…
就这样,从根目录开始,每一个文件夹中包含多个文件夹,形成了一个树状的结构。
为什么是小型文件系统?因默认每一个ZNode最多可存储1M数据,也可以将默认限制修改
ZNode介绍
ZNode可以看成是zookeeper作为文件管理系统时的文件路径
。由于层级结构,使得每一个Znode都有唯一
的路径,可以作为一个key
ZNode的类型
(临时
or持久
,普通
or序列
双双组合共4
种+)
-
持久节点:1. 创建出的一个节点,会永久存在。2. 可以创建子节点,类型任意
-
持久序号节点:在
持久节点的基础上
,创建节点时,会自动在指明的key
上带上一连串的数值,使得key唯一。这样在插入数据时,相同的key会被拼上序号,使之唯一。且这些序号会单调递增
-
临时节点 :1. 相比于
持久化节点
,服务器会在客户端断开
后,删除
这个客户端创建的所有
临时节点。2.不可创建子节点
- 流程:
- 流程:
-
临时序号节点: 1. 连接断开,数据即失 2. 不可有子节点 3. 创建的节点会被拼上序号,且序号单调递增
- 容器节点:当节点中在
60s
内仍没有任何子节点时 会被删除 - TTL节点:类似于redis在设定key时会指定有效期,超过有效期会被删除
ZNode结构
- data:节点的数据
- acl:权限:规定怎样的用户能够进行怎样的操作
- c:创建权限:是否允许在 该节点下创建
子节点
- w:更新权限:是否允许 更新
此节点
的数据
- r:读取权限:是否允许读取
该节点
数据以及子节点列表 - d:删除权限 :是否允许删除 该节点下的
子节点
数据 - a:admin权限:是否对该节点进行acl权限设置
- c:创建权限:是否允许在 该节点下创建
- stat:ZNode的元数据
- child:当前的子节点
搭建zookeeper
单节点
将下载好的压缩包解压
要先修改其配置文件,与redis类似,要使用读取配置文件启动,若不指定,默认在软件包下的/conf/zoo.cfg
,而软件包给的默认配置文件是名为zoo_sample
,目的是给出一个模板,由自己去修改。
常用参数:
参数 | 默认 | 描述 |
---|---|---|
dataLogDir | 事务日志 存放目录, 如果没有将会放在dataDir中 | |
preAllocSize | 64M | 每个事务日志 文件默认开辟空间,如果快照频率较大(因为每次快照就切换日志文件),可以将每个日志缩小容量,防止大量日志占用固定空间,但没有写满 |
globalOutstandingLimit | 1000 | 请求 堆积数,尽管zk没有空闲时间去处理请求,但还是允许请求进行提价,以提高吞吐性能,加以限制又防止内存溢出 |
snapCount | 100000 | 快照频率 ,当记录的事务日志达到这个次数时,会进行一次拍摄快照。但这个数并不是一定的,因为zk防止集群同时拍摄快照,通常在这个范围随机 |
maxClientCnxns | 10 | 客户端最大并发客户端数 |
服务端常用命令:(单节点)
- 启动 : ./zkServer.sh
start
【配置文件路径】 - 查看状态 : ./zkServer.sh
status
【配置文件路径 】 - 停止服务器:./zkServer.sh
stop
【配置文件路径】
客户端常用命令:(单节点)
要想能够连接成功,要先确保服务端已经启动
- 连接服务端:
./zkCli.sh
- 退出客户端 :
quit
搭建zookeeper集群
zookeeper的个数应选取奇数个,因为zookeeper集群的工作机制是 一个leader
带领 多个follower
,如果leader从集群脱离,在从follow中进行选举。而选举充分体现了zookeeper的一大特点半数以上机制
。只有总服务器数的一半以上
正常才可以
例:
- 共有5台服务器,leader宕机,在选举过程中,只有大于2.5台正常即 3台正常才可以。代表可以最多
有2台
宕机 - 共有6台服务器,leader宕机,在选举过程中,只有大于3台正常即4台正常才可以。代表可以做多
有2台
宕机
这样 在集群高可用方面,5台与6台都只能容纳2台服务器宕机。在集群可用性上 相当于浪费一台服务器,因此集群搭建通常为奇数
个服务器
集群规划
ip | 端口 |
---|---|
本地 | 3001 |
本地 | 3002 |
本地 | 3003 |
由于zookeeper集群还需要服务编号、leader与follwer信息同步端口、选举端口
服务编号号 | ip | 端口 | 信息同步端口 | 选举端口 |
---|---|---|---|---|
1 | 本机 | 3001 | 4001 | 5001 |
2 | 本机 | 3002 | 4002 | 5002 |
3 | 本机 | 3003 | 4003 | 5003 |
这样通过修改端口号,达到了在一个主机部署伪集群的效果
具体流程:
-
创建三个目录,在每个目录下创建一个文件
myid
,文件中只写入当前主机的服务编号
MyID中的数字大小十分重要,有关选举 -
修改配置文件,并将配置文件分发给各个节点
3. 启动集群 就是通过配置文件的方式将各个主机启动
为了简化启动方式,可以将可这写命令统一写入一个shell脚本 例:
#!/bin/bash
case $1 in
"start"){
sh /opt/zookeeper-3.5.7/bin/zkServer.sh start /opt/zookeeper-3.5.7/bin/zk-zone/z1/zoo.cfg
sh /opt/zookeeper-3.5.7/bin/zkServer.sh start /opt/zookeeper-3.5.7/bin/zk-zone/z2/zoo.cfg
sh /opt/zookeeper-3.5.7/bin/zkServer.sh start /opt/zookeeper-3.5.7/bin/zk-zone/z3/zoo.cfg
};;
"stop"){
sh /opt/zookeeper-3.5.7/bin/zkServer.sh stop /opt/zookeeper-3.5.7/bin/zk-zone/z1/zoo.cfg
sh /opt/zookeeper-3.5.7/bin/zkServer.sh stop /opt/zookeeper-3.5.7/bin/zk-zone/z2/zoo.cfg
sh /opt/zookeeper-3.5.7/bin/zkServer.sh stop /opt/zookeeper-3.5.7/bin/zk-zone/z3/zoo.cfg
};;
"status"){
sh /opt/zookeeper-3.5.7/bin/zkServer.sh status /opt/zookeeper-3.5.7/bin/zk-zone/z1/zoo.cfg
sh /opt/zookeeper-3.5.7/bin/zkServer.sh status /opt/zookeeper-3.5.7/bin/zk-zone/z2/zoo.cfg
sh /opt/zookeeper-3.5.7/bin/zkServer.sh status /opt/zookeeper-3.5.7/bin/zk-zone/z3/zoo.cfg
};;
esac
客户端连接集群 :./zkCli.sh -server
集群中的一台主机:其端口
与redis不同,集群中只能有一个Leader,其余全部为Follow,这就导致集群只能提升 读性能
、高可用
,而不能提升写性能
和集群自动伸缩 (因为配置文件中规定集群有哪些主机 在集群启动前已经写死!)
集群选举机制
zookeeper集群采用的类似于redis主从集群
采用读写分离的机制,但不同的是一个集群只能有一个leader,这个leader也是通过集群自动
选举产生的,不用去手动指定
zookeeper有3种角色:
- leader:负责集群中的所有事物,一个集群只能有一个
leader
- follower:负责
读
操作,如果leader不可用,可参与选举 - observer:除
不可选举外
,与follow相同。可以在配置集群时,手动指定其角色 例server.4=172.16.253.54:2004:3004:observer
。此角色的zookeeper在只能提升集群的读性能,但不能提升集群的高可用 (observer中文翻译:观察者)
zookeeper有4种状态:
- Observer:具有
Observer
角色的主机将从始至终都会保持Observer
这状态,不参与选举,没有状态转换
- Looking:在处于寻找
Leader角色
的状态,此时正在进行选举。根据选举结果进行状态转换。由于没有Leader无法完成任何操作,此时集群不可用- 常发生在 : 1.服务集群刚刚启动 2. 集群中Leader脱离集群(如:宕机)
- Leading:主节点,负责所有事物相关的操作,将信息同步给follow节点。
- Following:从节点,负责所有
读操作
,与Leader保持数据同步
。当Leader脱离集群会转为Looking
状态
只有在两个情况下需要进行选举(或者这种情况能使集群中节点转为Looking状态)
至少启动集群的半数以上
主机才能够正常选举
每个选票的构成:
整体流程:
- 第一次启动时:
不同于redis的手动slaveof masterIp masterPort
,zookeeper的第一次选举也是自动选举的
在选票未达到集群主机数的半数以上
时,每一次有其他主机加入,都会重新进行一次选举
集群中每个主机都只有一张票,先投给自己后期在进行更改
例:各个主机配置文件中指定集群有5台服务器(选举成功要求 选票>2.5 即 3),假设id分别为 1、2、3 、4、 5,且按照编号顺序启动
- 主机1 启动,处于Looking状态,先将自己的票投给自己,发现没有可以交换的节点,票数也达不到要求,就以Looking状态等待
- 主机2启动,处于Looking状态,重新选举,先将自己的票投给自己,与1进行交换,主机1发现主机2比自己的MyId大,将选票投给主机2,此时主机2有2票(自己+主机1)
- 主机3启动,处于Looking状态,重新选举,先将自己的票投给自己,与1、2 进行交换后,主机1、2一致认为主机3 的 MyId大,够投给主机3。此时主机3有三票,已经达到了半数以上,从此主机1、主机2修改自己的状态为
Follow
,主机3修改自己的状态为Leader
。集群能够正常运行 - 主机4启动,
发现Leader
(一选举出Leader,将不在重新选举),修改自己状态为Follow - 主机5启动,发现Leader,修改自己状态为Follow
遇强改投
疑问:1、是每一次都要进行选举彼此进行两两比较 还是要保存上一次选举信息,拿票数最高的与之比较
2、
- 非第一次启动时:
当集群中 Leader脱离集群时,会进行再一次选举。
但怎样判断是leader脱机集群而不是Follow自己脱离集群了呢?又是半数以上
原则:要想能够选举需要与集群其他节点通信,半数以上的节点一致认为Leader消失,如果只是节点当方面认为,会进行再次与Leader连接
如果半数以上与Leader失去联系,则重新选举
同样先比较事务ID
,id最大的赢得选举成为Leader,其余为Follow;若事务Id相同,则比较MyId,大者胜出
验证:
条件:
MyId | ip | 端口 | 信息同步端口 | 选举端口 |
---|---|---|---|---|
1 | 本机 | 3001 | 4001 | 5001 |
2 | 本机 | 3002 | 4002 | 5002 |
3 | 本机 | 3003 | 4003 | 5003 |
怎样让MyId为3的主机成为Leader呢? 根据选举流程,让其成为第二个启动的主机即可
主从数据同步
redis主从集群的数据同步:master节点负责所有写的任务,master节点写入成功后,再将写的数据同步给slave节点
这样就导致一个问题:可能正在准备同步时,master节点宕机导致数据丢失,所有slave都没有同步到新的数据,slave成为master后仍少了一部分数据,存在数据不一致的可能
zookeeper主从同步
:
写数据由Leader完成,但连接如果连接的是follow节点,进行写操作。follow服务器会将请求转发给Leader操作,Leader操作完毕后将结果再转交给此Follow
集群间进行写操作 遵从半数原则
,leader接收到数据后并不直接写入内存
,而是写入缓存给返回给Leader一个ACK(即自己),然后将数据转发给follow,follow接收到数据后同样也是写入缓存,向Leader发送一个ACK。当leader接收到一半以上
的ACK时,就认为写成功 返回成功信息
。Leader 紧接着发送一个Commit
,使得各个主机写在缓存中的数据放入内存。类似于sql的事务提交,只是这里要求半数以上即可。
问题::要求是一半以上即可,那么剩余一半以内的follow仍有可能丢失数据。Zookeeper采用的是 数据最终一致性
,当大部分节点保留最新数据,其他没有写成功的节点后续可以进行同步,最终会使得数据一致
CAP理论
一个分布式系统,不可能同时满足
- C:一致性(Consistency):消息只要查询,每个节点数据都是一致的
- A:可用性 (Availability):在正常的响应时间(时效性)
- P:分区容错性(Partition tolerance):集群部署(冗余部署)
- 如果集群部署(P),要严格执行C(一致性),那么在所有节点同步完成之前,应禁止读取,这就失去了可用性,违背了(A)
- 如果只有一个节点就好了,不用去考虑数据同步牺牲的时间,能够将写与读接连完成(AP)。返回是写入成功就是全部写入成功。但单节点出故障将导致不可用,违背了P
- 如果集群部署(P),严格执行A,那么为了响应时间只能异步同步数据,这就可能在返回成功信息后,进行异步同步,在这期间有请求访问没有同步成功的节点,造成数据不一致,违背了C
例:redis主从同步就是选择了AP,牺牲了C;zookeeper主从同步在AC之间折了个中,比降低了A但提升了C,最终还是A,牺牲了C;采用单节点部署就可以实现AC,牺牲了P
AP就像消息队列一样,先返回结果,再进行异步处理
总结:集群环境下,只要先同步在返回结果就违背可用性;只要先返回在返回结果就违背一致性;二者兼得就不代表不需要数据同步,就不可能是集群
监听机制
Zookeeper客户端的使用
zkCli
先使用 zkCli.sh ip:port
连接上客户端
查看节点与数据
查看节点:只是相当于查看目录,不能获取目录中的数据
- 详细查看节点
ls -s 【路径】
- 查看此节点下所有目录(
包含子目录
):ls -R 【路径】
- 查看节点转态 :
stat【路径】
功能类似于 ls -s 【路径】
查看节点保存的数据
- 查看节点中保存的数据:
get 【path】
zookeeper原生客户端
ZkClient
- 引入依赖
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
2.创建连接对象
集群环境下:
创建节点
删除节点
查询信息
修改数据
监听
zkClient中的监听是永久的
get -s path
ls -s path
Curator
创建节点
创建节点前,要求父节点必须存在,若不存在,先创建父节点。
create 【路径】(【节点数据】)
-
创建
持久
节点默认
即为持久节点 -
创建
持久序号
节点:-s
-
创建
临时
节点:-e
-
创建
临时序号
节点:-s -e
-
创建
容器
节点:-c
,该节点如果没有数据or文件夹会在60
内删除
修改节点值
- 修改某个节点的具体值: set 【path】【value】
删除节点
-
删除
单个
节点:delete 【路径】
-
删除此节点下
所有
节点:deleteAll 【路径】
监听机制
监听是zookeeper一个十分重要的功能,有了监听机制客户端可以实时收到节点、数据变动的通知从而 获取变化信息执行对应的回调处理,正是通过此机制 ,使得zookeeper不仅可以存放数据,当数据有变动时,还可以通知客户端。加上此机制可以用来服务注册中心
、发布、订阅模型
、锁
等
在客户端有开启两个线程负责监听机制:
- 一个线程connet负责网络通信 将要监听的内容 发送给Zookeeper
- 一个线程listener负责监听Zookeeper端发来的监听内容的数据或路径变化信息,从而执行指定的回调方法
在服务端,接收到监听请求,会在该请求的hash表中插入被Watch的路径
监听特点:
监听是一次性的触发器
,监听后当有数据发生一次变化异步
通知所有
(一个节点被多个客户端监听)监听它的客户端随后失效。要想解决
一次性问题,就要在客户端监听回调的函数中对数据或节点进行再一次监听
,从而变成循递归
,实现重复监听一个数据- 将变化信息发送给客户端是
异步
的,这就带来一个问题,可能由于网络原因导致不同的客户端不在同一时刻监听到发生的变化。但zk会保证消息的有序性,当一个客户端在看到Watch事件之前是不会看到结点数据的变化的。只有客户端先
收到数据变化的异步消息,然后
才会在zk服务端发现中数据的变化。
例:服务端监听 i =1 的数据变化,i 变为2,将消息异步发送给客户端。为了不使得
服务端 由1变成2 而客户端没有监听到却不知自己在使用新数据,所以,在客户端接收到i变化的通知前,客户端看到的i仍然为1。使用有序性
解决异步消息带来的消息不一致问题
监听的对象分为两种:在zk内部维护这两张表
-
数据 get
- get -w 【路径】:监听此节点
数据
的变化 和此节点的删除
,只有一次
有效 。同获取数据也是用的get
命令、ls命令是负责节点的NodeDataChanged
:修改节点的数据值NodeDeleted
:当前节点被删除
- get -w 【路径】:监听此节点
-
子节点 ls
- ls -w 【路径】:监听此节点
直系子节点的目录
变化和此节点的删除
NodeChildrenChanged
:在节点下创建、删除子节点 (必须是直系
子节点,不能是孙节点)NodeDeleted
当前节点被删除
- ls -w -R 【路径】:监听
子节点及其所有层级目录
和此节点的数据
变化 和此节点的删除
NodeDataChanged
:当前节点数据变化NodeChildrenChanged
:任意层级子节点发生变化NodeDeleted
:当前节点被删除
- ls -w 【路径】:监听此节点
未待完续。。