文章目录
Zookeeper 是一个分布式协调框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。主要有如下两个核心的概念:文件系统数据结构 + 监听通知机制
ZooKeeper使用树形结构管理数据。以“/”作为树形结构的根节点。树形结构中的每一个节点都称为“znode”(目录节点)。文件系统中的目录可以存放/删除其他目录和文件,znode中可以存放/删除其他znode,也可以对应一个具体的值。znode和它对应的值之间是键值对的关系。
1. zookeeper的安装
①:下载并解压
wget https://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.5.8/apache-zookeeper-3.5.8-bin.tar.gz
tar -zxvf apache-zookeeper-3.5.8-bin.tar.gz
cd apache-zookeeper-3.5.8-bin
②:重命名配置文件 zoo_sample.cfg
cp zoo_sample.cfg zoo.cfg
③:启动zookeeper服务端
# 可以通过 bin/zkServer.sh 来查看都支持哪些参数
bin/zkServer.sh start conf/zoo.cfg
④:客户端连接zookeeper服务端
//如果是单机,可省略 -server ip:port
bin/zkCli.sh -server ip:port
2. zookeeper的节点类型
- 持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只要不手动删除该节点,他将永远存在 - 持久化顺序目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 - 临时目录节点
客户端与zookeeper断开连接后,该节点被删除 - 临时顺序目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号 - Container 节点
zookeeper 3.5.3 版本新增的,容器节点主要用来容纳字节点,如果没有给其创建子节点,容器节点表现和持久化节点一样,如果给容器节点创建了子节点,后续又把子节点清空,容器节点也会被zookeeper删除。定时任务默认60s 检查一次 - TTL 节点
默认禁用,只能通过系统配置 zookeeper.extendedTypesEnabled=true 开启,不稳定
3. zookeeper命令解析
①: 创建zookeeper 节点 (create)
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
中括号为可选项,没有则默认创建持久化节点
- -s: 顺序节点
- -e: 临时节点
- -c: 容器节点
- -t: 可以给节点添加过期时间,默认禁用,需要通过系统参数启用
//系统参数如下:
-Dzookeeper.extendedTypesEnabled=true, znode.container.checkIntervalMs : (Java system property only) New in 3.5.1: The time interval in milliseconds for each check of candidate container and ttl nodes. Default is "60000".
//创建持久化节点
create /node data //创建带值目录节点
create /node //只创建目录节点
//创建持久化顺序节点
create -s /node data //创建带值顺序目录节点
create -s /node //只创建顺序目录节点
//创建临时目录节点
create -e /ephemeral data
//创建临时顺序目录节点
create -e -s /ephemeral data
//创建容器节点
create -c /container
②:查看节点数据 (get)
get /test-node
③:修改节点数据 (set)
set /test-node some-data-changed
④:查看节点目录(ls)
ls /test-node //查看test-node下的节点
ls -R /test-node //查看test-node下的所有节点,包括子节点下的节点
⑤:查看节点状态 (stat)
stat /test-node //只查看节点状态
get -s /test-node //查看节点状态信息同时查看数据
节点状态详情如下:
cZxid
:创建znode的事务ID(Zxid的值)mZxid
:最后修改znode的事务ID。pZxid
:最后添加或删除子节点的事务ID(子节点列表发生变化才会发生改变)。ctime
:znode创建时间。mtime
:znode最近修改时间。dataVersion
:znode的当前数据版本。cversion
:znode的子节点结果集版本(一个节点的子节点增加、删除都会影响这个版本)。aclVersion
:表示对此znode的acl版本。ephemeralOwner
:znode是临时znode时,表示znode所有者的 session ID。 如果znode不是临时znode,则该字段设置为零。dataLength
:znode数据字段的长度。numChildren
:znode的子znode的数量。
通过版本号判断节点是否被修改过
/test-node
当前的数据版本是 1 , 这时客户端 用 set
命令修改数据的时候可以把版本号带上
如果在执行上面 set
命令前, 有人修改了数据,zookeeper 会递增版本号, 这个时候,如果再用以前的版本号去修改,将会导致修改失败,报如下错误
4.zookeeper的监听通知机制
- 针对节点的监听 :一定事件触发,对应的注册立刻被移除,所以事件监听是一次性的
get -w /path // 注册监听的同时获取数据
stat -w /path // 对节点进行监听,且获取元数据信息
- 针对目录的监听:如下图,目录的变化,会触发事件,且一旦触发,对应的监听也会被移除,后续对节点的创建没有触发监听事件
ls -w /path
- 针对递归子目录的监听
ls -R -w /path // -R 区分大小写,一定用大写
如下对/test
节点进行递归监听,但是每个目录下的目录监听也是一次性的。
如第一次在/test
目录下创建节点时,触发监听事件,第二次则没有,同样,因为时递归的目录监听,所以在/test/sub0
下创建节点/test/sub0/subsub0
时,触发事件,但是再次创建/test/sub0/subsub1
节点时,没有触发事件。
Zookeeper事件类型
None
: 连接建立事件NodeCreated
: 节点创建NodeDeleted
: 节点删除NodeDataChanged
:节点数据变化NodeChildrenChanged
:子节点列表变化DataWatchRemoved
:节点监听被移除ChildWatchRemoved
:子节点监听被移除
5. zookeeper的常规配置
打开zookeeper的配置文件zoo.cfg
配置说明:
-
tickTime=2000
:zookeeper时间配置中的基本单位为2秒钟 (毫秒) -
initLimit=10
:允许follower初始化连接到leader最大时长为20秒,它表示tickTime时间倍数 即:initLimit*tickTime = 20秒 -
syncLimit=5
: 允许follower与leader数据同步最大时长,它表示tickTime时间倍数 -
dataDir=/tmp/zookeeper zookeper
: 数据存储目录 -
clientPort=2181
:对客户端提供的端口号 -
maxClientCnxns=60
: 单个客户端与zookeeper最大并发连接数 -
autopurge.snapRetainCount=3
: 保存的数据快照数量,之外的将会被清除 -
autopurge.purgeInterval=1
: 自动触发清除任务时间间隔,小时为单位。默认为0,表示不自动清除。
6. zookeeper的数据持久化
Zookeeper数据的组织形式为一个类似文件系统的数据结构,而这些数据都是存储在内存中的,所以我们可以认为,Zookeeper是一个基于内存的小型数据库
内存中的数据:
public class DataTree {
private final ConcurrentHashMap<String, DataNode> nodes =
new ConcurrentHashMap<String, DataNode>();
private final WatchManager dataWatches = new WatchManager();
private final WatchManager childWatches = new WatchManager();
DataNode
是Zookeeper
存储节点数据的最小单位
public class DataNode implements Record {
byte data[];
Long acl;
public StatPersisted stat;
private Set<String> children = null;
然而如果数据只存储在内存中的话,那么在zookeeper
宕机或者断电的情况下,数据将会丢失,所以zookeeper
也制定了一些数据持久化方式:
- 事务日志log
- 数据快照snapshot
6.1 事务日志log文件
针对每一次客户端的事务操作(写),Zookeeper
都会将他们记录到事务日志中,当然,Zookeeper
也会将数据变更应用到内存数据库中。我们可以在zookeeper
的主配置文件zoo.cfg
中配置内存中的数据持久化目录,也就是事务日志的存储路径 dataLogDir
. 如果没有配置dataLogDir
(非必填), 事务日志将存储到dataDir
(必填项)目录。
dataDir=/tmp/zookeeper/snapshot //数据快照目录
dataLogDir=/tmp/zookeeper/log //事务日志目录
当 ZK
启动后会基于上面两个路径继续创建 version-2
子路径,之后的文件都会在该子路径下创建
/tmp
└── zookeeper
# 数据快照
├── snapshot
└── version-2
└── ...
# 事务日志
└── log
└── version-2
└── ...
事务日志 log
文件名的格式是这样 log.{zxid}
,其中zxid
对应当时创建该文件时的最大 zxid
,假设现在创建时 zxid
为 0,那目录结构会是这样:
/tmp
└── zookeeper
└── log
└── version-2
└── log.0
Zookeeper
进行事务日志文件操作的时候会频繁进行磁盘IO
操作,事务日志的不断追加写操作会触发底层磁盘IO
为文件开辟新的磁盘块,即磁盘Seek
。
因此,为了提升磁盘IO
的效率,Zookeeper
在创建事务日志文件的时候就进行文件空间的预分配- 即在创建文件的时候,就向操作系统申请一块大一点的磁盘块。这个预分配的磁盘大小可以通过系统参数 zookeeper.preAllocSize
进行配置。
zookeeper.txnLogSizeLimitInKb
这个环境变量配置,默认是 -1,这个配置限制了 log 单个文件大小(单位是 KB),如果用户手动配置了该参数,就会检查当前 log 文件大小是否超过了该参数大小,如果超过了就会进行 rollLog
,相当于下一次的写请求会创建一个新的 log
文件。除此之外, 每次快照的时候会强制执行一次 rollLog
。
6.2 数据快照snapshot
数据快照用于记录Zookeeper
服务器上某一时刻的全量数据,并将其写入到指定的磁盘文件中。数据存储在dataDir
指定的目录中,snapshot
文件名的格式是 snapshot.{zxid}
,那么 zxid
对应当是创建该文件时的最大 zxid
,假设现在创建是最大 zxid
是 0
,那目录结构会是这样:
/tmp
└── zookeeper
└── snapshot
└── version-2
└── snapshot.0
首先有两个配置
zookeeper.snapCount
(默认100000
)zookeeper.snapSizeLimitInKb
(默认4194304
单位是KB
,相当于 4 GB)
在启动后会基于这两个配置分别生成两个随机数,假设上述的配置是按照默认的设置,这两个随机数的范围就是:
randRoll = [0, 50000]
randSize = [0, 4194304 * 1024 / 2]
可以简单的认为就是上述两个配置的一半之内的随机数,至于 randSize
为什么要乘以 1024
因为最终文件计算大小是以 byte
作为单位的。
而是否快照就是取决于上面两个随机数,有两个条件:
- 当前写请求的数量达到了
zookeeper.snapCount
的一半并加上 randRoll 的数量 - 当前 log 文件的大小达到了
zookeeper.snapSizeLimitInKb
的一半并加上 randSize 的大小
上述条件满足任意一个条件后就会重置上面的两个随机数,并开始生成快照,生成快照这个过程是启动一个子线程去创建的。
6.3 zookeeper数据恢复过程
上述两种持久化文件另一个重要用途就是帮助 ZK 恢复服务端的信息。
在 ZK 启动的时候就会尝试读取 dataDir
和 dataLogDir
这两个目录下的文件,假设在这两个路径下的文件是:
/tmp
└── zookeeper
├── snapshot
└── version-2
└── snapshot.5
└── snapshot.37
└── snapshot.100
└── log
└── version-2
└── log.0
└── log.6
└── log.38
└── log.90
└── log.108
log
负责记录每一个写请求snapshot
负责对当前整个内存数据进行快照- 恢复数据的时候,会先读取最新的
snapshot
文件 - 然后在根据
snapshot
最大的 zxid 去搜索符合条件的log
文件,再通过逐条读取写请求来恢复剩余的数据