zookeeper基本概念
文章目录
一:并发环境下面临的挑战
其实我们把线程换成进程,相当于每台服务上跑了一个程序,相同的应用程序运行于多个服务器集群上,是为了解决单台服务面对高并发处理不来的情况。而尝试去处理这些情况,我们就会面临很多诸如此类的问题:
- 比如说我们现在是3台服务器的一个集群, 怎么保证所有机器共享的配置信息保持一致?
- 有一台机器挂掉了,其他机器如何感知到这一变化并接管任务?
- 用户量突然的爆增,需要增加机器来缓解压力,如何做到不重启集群而完成机器的添加?
- 分布式系统,怎么高效协同多台服务对同一网络文件进行写操作(网络并不是即时的,存在延时)?
此时我们就需要一个类似于线程协同机制的能让进程进行协同的工具
二:Zookeeper的介绍
1:Zookeeper名字的由来
在apache上的许多开源项目都是以动物形象作为icon,比如tomcat就是一只猫,hive是只黄蜂等,zookeeper的工作就是把这些动物的行动进行协调
2:Zookeeper的简介
zookeeper就是一种用于分布式应用程序的高性能协调服务,它的特点就是数据是存于内存中的,持久化实现在日志中
它的内存类似于树形结构,且高吞吐低延迟,可以帮助我们实现分布式统一配置中心,服务注册,分布式锁等 组成ZooKeeper服务的服务器必须彼此了解
它们维护内存中的状态图像,以及持久性存储中的事务日志和快照
只要大多数服务器可用,ZooKeeper服务就可用【AP】。客户端连接到单个ZooKeeper服务器
客户端维护TCP连接,通过该连接发送请求,获取响应,获取监视事件以及发送tick。
如果与服务器的TCP连接中断,则客户端将连接到其他服务器。
3:Zookeeper的安装
- JDK版本需要在1.6以上
- 下载: https://archive.apache.org/dist/zookeeper/zookeeper-3.5.2/zookeeper-3.5.2.tar.gz
- 解压后的conf目录,增加配置文件zoo.cfg
- tickTime=2000:一次心跳的基本时间
- dataDir:数据与日志的存放处
- clientPort:端口号
- 启动服务端 bin/zkServer.sh start
- 测试,客户端连接: bin/zkCli.sh -server 127.0.0.1:2181
下面实际操作一下
1:下载安装包
2:解压并配置
tar -zxfv apache-zookeeper-3.5.7-bin.tar.gz
# 重命名文件夹
mv apache-zookeeper-3.5.7-bin zookeeper-3.5.7
# 进入conf目录,重命名`zoo_sample.cfg`为`zoo.cfg`,然后进入`zoo.cfg`
mv zoo_sample.cfg zoo.cfg
3:创建data目录并指定配置
mkdir zkData
bin ---> 包括了linux和window的运行程序的运行目录
conf ---> zookeeper的配置zoo.cfg
contrib ---> 其他一些组件和发行版本
dist-maven ---> maven发布下的一些jar包
docs ---> 文档
lib ---> 库
recipe ---> 一些应用实例
src ---> zookeeper的源码,因为zookeeper是java写出来的
vim zoo.cfg
下面是核心参数的解读
- tickTime -> 通信的心跳时间,zk服务器和客户端心跳时间,单位是ms
- initTime -> LF初始通信时限,Leader和Follower初始连接时容忍的最多心跳数
- syncLimit -> LF同步通信时限,Leader和Follower之间的通信时间如果超过了syncLimit * tickTime,Leader就会认为Follower死掉了,就会从服务列表中移除
- dataDir -> 保存zk的数据,默认是temp目录,容易被Linux系统定期删除,所以一般不用tmp目录
- clientPort -> 客户端连接接口,通常不做修改
4:Zookeeper特点
4.1:数据结构简单
类似于Unix文件系统树形结构,每个目录成为Znode节点,但它不同于文件系统,它既可以视为文件夹,也可以视为文件来存放数据,但是我们平时还是得叫它节点,别叫文件夹这么掉价。
需要注意:同一个节点下的子节点名称不能相同,且命名是有规范的,它的路径是没有相对路径的概念的,都是绝对路径,任何开始都以"/"开始,最后就是,它存放数据的大小是有限制的
4.2:数据模型特点
层次命名空间:就是上面已经提到的,类似于unix的文件系统,以"/"为根
节点可以包含关联数据和子节点,绝对路径 Znode:名称唯一
命名有规范,类型分4种:持久,顺序,临时,临时顺序,节点的数据构成之后再提
4.3:命名规范
- null字符(\u0000)不能作为路径名的一部分;
- 以下字符不能使用,因为它们不能很好地显示,或者以令人困惑的方式呈现:\u0001 - \u0019和\u007F - \u009F。
- 不允许使用以下字符:
\ud800 - uf8fff
,\uFFF0 - uFFFF
。 - “.”字符可以用作另一个名称的一部分,但是“.”和“…”不能单独用于指示路径上的节点,因为ZooKeeper不使用相对路径。下列内容无效:“/a/b/. / c”或“c / a / b / . . /”。
- “zookeeper”是保留节点名。
4.4:重要特点—有序
提供多种方式跟踪时间,ZooKeeper给每个更新贴上一个数字
这个数字反映了所有ZooKeeper事务的顺序,严格的顺序意味着可以在客户机上实现复杂的同步原语 解释czxid、version、zoo.cfg中ticks配置:
- Zxid :Zookeeper中每次写请求都对应一个唯一的事务id,称为 Zxid,它是全局的且有序的,如果 Zxid1 小于 Zxid2,那 Zxid1 就一定是发生在 Zxid2 前
- version numbers : 版本号,对节点的写请求都会导致该节点的3种版本号增加(其实套路和乐观锁差不多),dataVersion(对znode数据的更改次数),cversion(对znode子节点的更改次数),aclVersion(对znode ACL的更改次数
- ticks : 当使用多服务器Zookeeper时,服务器使用一个“滴答”来定义事件的时间,如状态上传,会话超时等,它通过最小会话超时(默认是滴答时间x2)间接公开,如果客户端请求超过这个时间,那客户端就不再能连接上服务器端
- real time:Zookeeper并不使用真实时间
你可以使用stat path
或者ls2
来查看这些信息:
cZxid:创建该节点的zxid
ctime:该节点的创建时间
mZxid:该节点的最后修改zxid
mtime:该节点的最后修改时间
pZxid:该节点的最后子节点修改zxid
cversion:该节点的子节点变更次数
dataVersion:该节点数据被修改的次数
aclVersion:该节点的ACL变更次数
aphemeraOwner:临时节点所有者会话id,非临时的为0
dataLength:该节点数据长度
numChildren:子节点数
4.5:重要特点—可复制
数据可复制,可备份。
zookeeper可以快速地搭建一个集群,内部自带了这样的一些工具与机制,我们只需要设置一些配置即可,保证服务可靠,不会成为单点故障
4.6:重要特点—迅速
zookeeper的一些特点可以应用于大型分布式系统
- zookeeper的数据加载在内存之中,这意味着zookeeper可以具备高吞吐和低延迟的效果
- 已读取为事务的话尤其的快
- 操作的znode大小的限制为1M
5:Zookeeper理论
5.1:zookeeper的会话机制
Session会话
- 一个客户端连接一个会话,由zookeeper分配唯一会话id
- 客户端以特定的时间间隔发送心跳以保持会话有效,
- 超过会话超时时间未收到客户端的心跳,则判断客户端无效(默认2倍tickTime)
- 会话中额请求是FIFO(先进先出原则)的顺序执行
5.2:znode的数据
- 节点数据:存储的基本信息(状态,配置,位置等)
- 节点元数据:stat命令下的一些数据
- 数据大小:限制1M
节点类型
- 持久节点:直接通过create path value所创建
- 临时节点:create -e path value
- 顺序节点:create -s path value
下面三点要注意:
- session会话失效时,临时节点就会被删除
- 顺序节点的创建,后为10位十进制序号,每个父节点拥有一个计数器,这个计数器也是有限制的,到2147483647之后将溢出
- 顺序节点在会话结束仍然存在
5.3:Watch监听机制
客户端能在znodes上设置watch,监听znode的变化,包括增删改查
通过stat path ,ls2 path get path皆可查看
触发watch事件的条件有4种,create,delete,change,child(子节点事件)
watch的重要特性:
一次性:
无论是服务端还是客户端,一旦一个 Watcher 被触发,Zookeeper 都会将其从相应的存储中移除。这样的设计有效的减轻了服务端的压力,不然对于更新非常频繁的节点,服务端会不断的向客户端发送事件通知,无论对于网络还是服务端的压力都非常大
客户端串行执行:
客户端 Watcher 回调的过程是一个串行同步的过程
轻量
Watcher 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容
客户端向服务端注册 Watcher 的时候,并不会把客户端真实的 Watcher 对象实体传递到服务端,仅仅是在客户端请求中使用 boolean 类型属性进行了标记
无法保证强一致性
watcher event异步发送 Watcher 的通知事件从 server 发送到 client 是异步的
这就存在一个问题,不同的客户端和服务器之间通过 socket 进行通信,由于网络延迟或其他因素导致客户端在不通的时刻监听到事件
由于 Zookeeper 本身提供了 ordering guarantee,即客户端监听事件后,才会感知它所监视 znode发生了变化。
所以我们使用 Zookeeper 不能期望能够监控到节点每次的变化。Zookeeper 只能保证最终的一致性,而无法保证强一致性
可能丢失
当一个客户端连接到一个新的服务器上时,watch 将会被以任意会话事件触发。
当与一个服务器失去连接的时候,是无法接收到 watch 的。
而当 client 重新连接时,如果需要的话,所有先前注册过的 watch,都会被重新注册。
通常这是完全透明的。只有在一个特殊情况下,watch 可能会丢失:对于一个未创建的 znode的 exist watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个 watch 事件可能会被丢失
客户端注册Watcher实现
- 调用
getData()
/getChildren()
/exist()
三个API
,传入Watcher
对象 - 标记请求
request
,封装Watcher
到WatchRegistration
- 封装成
Packet
对象,发服务端发送request
- 收到服务端响应后,将
Watcher
注册到ZKWatcherManager
中进行管理 - 请求返回,完成注册
服务端处理Watcher实现
- 服务端接收Watcher并进行存储
- 接收到客户端请求,处理请求判断是否需要注册 Watcher,需要的话将数据节点的节点路径和 ServerCnxn(ServerCnxn 代表一个客户端和服务端的连接,实现了 Watcher 的 process 接口,此时可以看成一个 Watcher 对象)存储在WatcherManager 的 WatchTable 和 watch2Paths 中去
- Watcher触发
以服务端接收到 setData()
事务请求触发 NodeDataChanged
事件为例:
- 将通知状态(SyncConnected)、事件类型(NodeDataChanged)以及节点路径封装WatchedEvent
- 从 WatchTable 中根据节点路径查找 Watcher
- 没找到;说明没有客户端在该数据节点上注册过 Watcher
- 找到;提取并从 WatchTable 和 Watch2Paths 中删除对应 Watcher【一次性体现】
- 调用 process 方法来触发 Watcher
客户端返回Watcher实现
- 客户端 SendThread 线程接收事件通知,交由 EventThread 线程回调 Watcher。
- 客户端的 Watcher 机制同样是一次性的,一旦被触发后,该 Watcher 就失效了。
三:Zookeeper客户端
1:创建永久 + 不带序号节点
create /sanguo "diaochan" # 创建一个持久化的znode,名称是/sanguo,里面放了一个值叫做 diaochan
Created /sanguo # 提示sanguo已经被创建
ls / # 查看根节点下有哪些节点
[sanguo, zookeeper] # 发现sanguo节点已经挂到根节点下面了
create /sanguo/shuguo "liubei" # 在sanguo这个znode下再创建一个shuguo的持久化znode,里面放一个值叫做liubei
ls / # 查看根节点下有哪些节点
[sanguo, zookeeper] # 发现还是这两个,因为shuguo是挂在sanguo下的,没直接挂在根节点下
ls /sanguo # 查看sanguo节点下有哪些节点
[shuguo] # 发现shuguo在这里
get -s /sanguo # 打印sanguo的信息
2:创建永久 + 带序号节点
create /sanguo/weiguo "caocao" # 在sanguo这个znode下创建一个持久化znode weiguo,并且挂接一个值是caocao
Created /sanguo/weiguo
ls /sanguo # 查看sanguo节点下有哪些节点
[shuguo, weiguo]
create -s /sanguo/weiguo/zhangliao "zhangliao" # 在weiguo下创建一个带序号的znode zhangliao,并且值是zhangliao
Created /sanguo/weiguo/zhangliao0000000000 # 发现张辽是带有序号的
ls /sanguo/weiguo
[zhangliao0000000000]
create -s /sanguo/weiguo/zhangliao "zhangliao" # 在weiguo下创建一个带序号的znode zhangliao,并且值是zhangliao
Created /sanguo/weiguo/zhangliao0000000001 # 发现序号是递增的
ls /sanguo/weiguo
[zhangliao0000000000, zhangliao0000000001]
3:创建临时不带序号节点
ls /sanguo
[shuguo, weiguo]
create -e /sanguo/wuguo "sunquan"
Created /sanguo/wuguo
ls /sanguo
[shuguo, weiguo, wuguo] # 客户端和服务端断开连接后,wuguo将会被删除
4:创建临时并且带序号节点
create -e -s /sanguo/wuguo/zhouyu "zhouyu"; # 创建一个临时带序号的节点
Created /sanguo/wuguo/zhouyu0000000002
5:修改节点
get -s /sanguo/weiguo
caocao
cZxid = 0x300000010
...
set /sanguo/weiguo "simayi" # 修改znode的值
get -s /sanguo/weiguo
simayi
cZxid = 0x300000010
...
6:删除节点
# 假设sanguo下现在有如下子节点,且每一个子节点下面不再有子节点
ls /sanguo
[shuguo, wuguo, weiguo, jin]
delete /sanguo/jin # 删除jin这个znode
ls /sanguo
[shuguo, wuguo, weiguo] # 发现已经没有了
delete /sanguo # 尝试使用delete删除有子节点的节点
Node not empty: /sanguo # 会提示节点不是空,不能删除
deleteall /sanguo # 如果要进行强行删除,可以使用递归删除deleteall
7:监听原理
监听节点数据的变化:get -w path [watch]
监听子节点的增减:ls -w path [watch]