0%

zookeeper-基本原理

介绍了zookeeper的基本原理和使用

简介

ZooKeeper是个分布式的服务协调框架。具体用途如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。它基于观察者的设计模式;zookeeper = 文件系统 + 监听通知机制。

特点:

  1. 最终一致性:Client不论连接到哪个Server,得到的数据都是一样的。
  2. 原子性:事务中的所有操作全部执行或者全部不执行。
  3. 顺序性:同一个client的请求顺序执行。
  4. 分区容错性:zookeeper是分布式的,所以在部分节点出现故障时,可以自己恢复。

数据结构

znode:zk是层级树状结构,一个节点就是znode。默认可存1M数据

  • 操作节点时可设置watcher。当节点状态发生变化时,就会触发watcher对应的操作,只触发一次。
  • 永久节点:创建后永久存在,除非主动删除;临时节点:临时创建的,会话结束节点自动被删除
  • 顺序节点:节点名称后面自动增加一个10位数字的序列号;非顺序节点:不加序列号

节点衍生出分类是为了迎合需求。临时节点可以记录服务器是否上线:当服务器下线,临时节点的数据消除。顺序节点可用于同一服务器多次上下线,每次名字都不同。很明显2个2分类,两两组合有4种节点。

下面是一个znode的数据结构:

# 一个znode的数据结构
[zk: localhost:2181(CONNECTED) 0] stat /zxy
cZxid = 0x39 # 节点创建时的zxid
ctime = Mon Sep 23 16:38:23 CST 2019 # 节点创建时的时间戳
mZxid = 0x3d # 节点修改时的zxid
mtime = Mon Sep 23 16:38:44 CST 2019 # 节点修改时的时间戳
pZxid = 0x39 # 改变子节点时的zxid
cversion = 0 # 子节点的版本号
dataVersion = 3 # 数据版本:初始0,改变一次加1
aclVersion = 0 # ACL版本(权限)
ephemeralOwner = 0x0 # 数据拥有者:永久节点是0;临时节点是创建者的id
dataLength = 13 # 数据长度
numChildren = 0 # 子节点个数

# 对节点的操作无非是增删改查等,这里就不写了

补充:

zxid(ZooKeeper Transaction Id):ZooKeeper每次状态变化将会产生一个叫zxid的时间戳。

原理

基本流程:

客户端发请求(可带watcher) -> zk 选举与恢复 (没leader时) -> zk 读写数据 -> 返回数据(可触发watcher)

两端主要是监听器的原理,中间主要用到ZAB协议

监听器原理

watcher 相当于一个的炸弹,客户端发请求时:如ls,get,set等,可以给节点绑上炸弹。

如果触发了爆炸条件:ls就是该节点有增加删减子节点;get set就是该节点数据改变。

炸弹爆炸(只炸一次)也就是执行里面的回调函数。

很明显这里用户端至少需要启动两个线程:connect线程负责网络连接通信:绑炸弹并把任务发给zk与后续数据互传;listener线程监听炸弹爆炸信号。

zk.png

ZAB协议

Zab协议(Zookeeper Atomic Broadcast),通过它来保证分布式事务的最终一致性。
这个内容很多,详细的可以参考:

Zookeeper——一致性协议:Zab协议

Zookeeper的功能以及工作原理

主要功能:崩溃恢复(选举、数据恢复) 的 原子广播 (数据读写)

崩溃恢复

集群中必须有一个leader,leader出现故障时,采用投票选举新leader,它需要满足以下条件:

  • 新选举出来的 Leader 不能包含未提交的 Proposal 。
  • 新选举的 Leader 节点中含有最大的 zxid 。
  • 得到超过一半选票者称为 Leader,因此zk集群个数为奇数

并不是所有节点都是 leader 和 follower ,还有observer,它不参与选举。作用是:可以增加集群数量,又减少投票选举时间。

选出leader后,进行数据恢复也就是同步,这个没啥,就是让它们其它节点数据都和leader同步,毕竟咱要确保最终一致性。恢复完毕后,就可以处理客户端的请求了。

读写数据

一般流程:

读请求,就是直接从当前节点中读取数据

写请求

  1. 客户端发起一个写操作请求。
  2. Leader 将客户端的请求转化为事务(Proposal),每个 Proposal 分配一个全局的ID,即zxid。
  3. Leader 为每个 Follower 分配一个单独的队列,然后将需要广播的 Proposal 依次放到队列中取,并且根据 FIFO 策略进行消息发送。
  4. Follower 接收到 Proposal 后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向 Leader 反馈一个 Ack 响应消息。
  5. Leader 接收到超过半数以上 Follower 的 Ack 响应消息后,即认为消息发送成功,可以发送 commit 消息。
  6. Leader 向所有 Follower 广播 commit 消息,同时自身也会完成事务提交。Follower 接收到 commit 消息后,会将上一条事务提交。

有意思的点

  • **如何保证消息有序:**在整个消息广播中,Leader会将每一个事务请求转换成对应的 proposal 来进行广播,并且在广播 事务Proposal 之前,Leader服务器会首先为这个事务Proposal分配一个全局单递增的唯一ID,称之为事务ID(即zxid),由于Zab协议需要保证每一个消息的严格的顺序关系,因此必须将每一个proposal按照其zxid的先后顺序进行排序和处理。

  • **用队列提高效率:**Leader 服务器与每一个 Follower 服务器之间都维护了一个单独的 FIFO 消息队列进行收发消息,使用队列消息可以做到异步解耦。 Leader 和 Follower 之间只需要往队列中发消息即可。如果使用完全同步的方式会引起阻塞,性能要下降很多。(我感觉这里应该不是FIFO 消息队列,应该是最小队列吧)

  • 记住超过半数