分布式Zookeeper-基础

分布式Zookeeper-基础

Zookeeper简介

Zookeeper是什么

Zookeeper为分布式应用提供高效且可靠的分布式协调服务,而是采用了名为 ZAB 的一致性协议。作用主要是维护和监控存储数据的状态变化,一旦变化就Zookeeper就负责将已经在Zookeeper上注册的观察者做出想要的反应(ps 观察者模式) 通过监控这些数据状态变化,从而达到基于数据的集群管理

Zookeeper相关特性

  • 高可用: 集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。所以Zookeeper适合安装奇数台服务器
  • 全局数据一致: 每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
  • 原子性:所有事务请求的处理结果在整个集群中任何机器上的应用情况是一致的,即整个集群要么都成功应用了某个事务,要么都没有应用。
  • **顺序一致性:**自同一个Client的更新请求按其发送顺序依次执行。

核心概念

数据模型

ZooKeeper 的数据模型是一个树形结构的文件系统。树中的节点被称为znode,其中根节点为/,每一个节点上都会保存自己的数据和节点信息。znode被用于存储数据,并且有一个与之相关联的 ACL(用作权限控制来使用),存储数据的大小被限制在1MB以内。znode通过路径唯一标识

分布式Zookeeper-基础

节点信息

节点有两种类型

  • 持久(Persistent):客户端和服务器端断开连接后,创建的节点不删除
  • 短暂(Ephemeral):客户端和服务器端断开连接后,创建的节点自己删除

创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护,在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序。利用 顺序号(zxid) 实现了严格的顺序访问控制能力。

属性信息

分布式Zookeeper-基础

节点属性 注解
cZxid 数据节点被创建时的事务Id
ctime 数据节点创建时间
mZxid 数据节点被修改时最新的事物Id
mtime 数据节点最后修改时间
pZxid 节点的父级节点事务Id
cversion 子节点版本号(子节点修改次数,每修改一次值+1递增)
dataVersion 当前节点版本号(每修改一次值+1递增)
aclVersion 当前节点acl版本号(节点被修改acl权限,每修改一次值+1递增)
ephemeralOwner 临时节点标示,当前节点如果是临时节点,则存储的创建者的会话id(sessionId),如果不是,那么值=0
dataLength 节点所存储的数据长度
numChildren 节点下子节点的个数

集群角色

Zookeeper 集群是一个基于主从复制的高可用集群,每个服务器承担如下三种角色中的一种。

  • Leader它负责 发起并维护与各 Follwer 及 Observer 间的心跳。所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器。一个 Zookeeper 集群同一时间只会有一个实际工作的 Leader。
  • follower它会响应 Leader 的心跳。Follower 可直接处理并返回客户端的读请求,同时会将写请求转发给 Leader 处理,并且负责在 Leader 处理写请求时对请求进行投票。一个 Zookeeper 集群可能同时存在多个 Follower。
  • Observer角色与 Follower 类似,但是无投票权。

ACL

ZooKeeper 采用 ACL(Access Control Lists)策略来进行权限控制(znode级别的)。类似UNIX系统 每一个znode创建的时候都会带一个ACL列表,决定谁可以对它执行何时操作,ACL主要依赖于Zookeeper的客户端认证机制。ZooKeeper提供一下几种认证方式

  • world:表示任何人都可以访问
  • digest: 用户名和密码 来识别客户端
  • auth:只有认证的用户可以访问
  • iP:通过 IP 来识别客户端

ZooKeep定义了如下五种权限

  • **CREATE:**允许创建子节点
  • **READ:**允许从节点获取数据并列出其子节点
  • WRITE: 允许为节点设置数据
  • **DELETE:**允许删除子节点
  • ADMIN: 允许为节点设置权限

工作原理

选举机制

Zookeeper的leader选举存在两个阶段,一个是服务器启动时候Lead选举,另一个是运行过程中leader服务器宕机。

选举过程中有一些几个重要参数

  • 服务器 ID(myid):编号越大在选举算法中权重越大,机器唯一标识
  • 事务 ID(zxid):值越大说明数据越新,权重越大
  • 逻辑时钟(epoch-logicalclock):同一轮投票过程中的逻辑时钟值是相同的,每投完一次值会增加,就是Leader任期代号

选举Leader的规则

  1. epoch大的直接胜出
  2. epoch相同,事务id大的胜出
  3. 事务id相同,服务器id大的胜出

选举状态

  1. Looking:竞选状态
  2. FOLLOWING: 随从状态,同步 leader 状态,参与投票
  3. OBSERVING: 观察状态,同步 leader 状态,不参与投票
  4. LEADING: 领导者状态
服务启动时候leader选举

假设我们先用有五台服务器,如下图所示

分布式Zookeeper-基础

  • 服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为looking状态

  • 服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的myid比自己目前投票推举的(服务器1)

    大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持looking状态

  • 服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服

    务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为following状态,服务器3更改状态为leading状态

  • 服务器4启动,发起一次选举。此时服务器1,2,3已经不是looking状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为following状态;

  • 服务器5启动,流程和服务器4一样

运行过程中leader选举

集群中leader服务器出现宕机或者不可用,整个集群无法对外提供,进入新一轮的leader选举

  1. 变更状态。leader 挂后,其他非 Oberver服务器将自身服务器状态变更为 LOOKING。
  2. 每个 server 发出一个投票。在运行期间,每个服务器上 zxid 可能不同。
  3. 处理统计投票。规则同启动过程。
  4. 改变服务器状态。与启动过程相同

监听器原理

分布式Zookeeper-基础

Zookeeper 允许客户端向服务端的某个Znode注册一个Watcher监听,当服务端的节点发生变化会触发了这个Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能(ps 这玩意也就一次,再次变化客户端如果不重新注册的话,就接受不到,大概步骤如下

  • 客户端发送事件通知请求
    • 客户端发送的监听请求会放到队列中(outgoingQueue),队列会有一个 sendThread 处理,sendThread 通过发送 path(Zk节点的位置) 路径到 server 注册 watch 事件
  • zk服务端会有一个 WatchManger 类,类中中有 HashMap<String,HashSet> watchTable ,key为path , Watcher 是一个客户端网络连接封装,当节点变化时会通知对应的连接(连接通过心跳保持)。
  • 客户端发送完后本地会有一个 ZKwatcherManager 类,该类中保存了 Map<String,Set> dataWatchers、Map<String,Set> existsWatchers、Map<String,Set> childrenWatchers 三个集合,客户端会在 dataWatchers 中会添加一个 key 为 path 路径的本地事件
  • 当服务端节点变化时会从 watchTable 中拿到对应 path 的 Watcher 集合通知到客户端,通知完后就会删除改相关连接,客户端根据返回结果中的 path 拿到 dataWatchers 中的本地事件进行调用。

读写原理

读操作

Leader/Follower/Observer 都可直接处理读请求,从本地内存中读取数据并返回给客户端即可。由于处理读请求不需要服务器之间交互,Follower/Observer越多,整体系统的读请求吞吐量越大,读性能越好。

写操作

所有写请求实际上都要交给 Leader 处理,Leader 将写请求以事务形式发给所有 Follower 并等待 ACK,一旦收到半数以上 Follower 的 ACK,即认为写操作成功。

写Leader
  • 客户端向 Leader 发起写请求
  • Leader 将写请求以事务 Proposal 的形式发给所有 Follower 并等待 ACK
  • Follower 收到 Leader 的事务 Proposal 后返回 ACK
  • Leader 得到过半数的 ACK(Leader 对自己默认有一个 ACK)后向所有的 Follower 和 Observer(无投票权) 发送 Commmit
  • Leader 将处理结果返回给客户端
写Follower/Observer

Follower/Observer 均可接受写请求,但不能直接处理,而需要将写请求转发给 Leader 处理,走lead的处理流程,除了多了一步请求转发,其它流程与直接写 Leader 无任何区别。

事务

为了保证事务的顺序一致性,ZooKeeper 采用了递增的事务 id 号(zxid)来标识事务。

**Leader 服务会为每一个 Follower 服务器分配一个单独的队列,然后将事务 Proposal 依次放入队列中,并根据 FIFO(先进先出) 的策略进行消息发送。**Follower 服务在接收到 Proposal 后,会将其以事务日志的形式写入本地磁盘中,并在写入成功后反馈给 Leader 一个 Ack 响应。**当 Leader 接收到超过半数 Follower 的 Ack 响应后,就会广播一个 Commit 消息给所有的 Follower 以通知其进行事务提交,**之后 Leader 自身也会完成对事务的提交。而每一个 Follower 则在接收到 Commit 消息后,完成事务的提交。

所有的提议(proposal)都在被提出的时候加上了 zxid。zxid 是一个 64 位的数字,它的高 32 位是 epoch 用来标识 Leader 关系是否改变,每次一个 Leader 被选出来,它都会有一个新的 epoch,标识当前属于那个 leader 的统治时期。低 32 位用于递增计数。

详细过程如下

  • Leader 等待 Server 连接
  • Follower 连接 Leader,将最大的 zxid 发送给 Leade
  • Leader 根据 Follower 的 zxid 确定同步点
  • 完成同步后通知 follower 已经成为 update 状态
  • Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了

会话

ZooKeeper 客户端通过 TCP 长连接连接到 ZooKeeper 服务集群。会话 (Session) 从第一次连接开始就已经建立,之后通过心跳检测机制来保持有效的会话状态。通过这个连接,客户端可以发送请求并接收响应,同时也可以接收到 Watch 事件的通知。

每个 ZooKeeper 客户端配置中都配置了 ZooKeeper 服务器集群列表。启动时,客户端会遍历列表去尝试建立连接。如果失败,它会尝试连接下一个服务器,依次类推。

一旦一台客户端与一台服务器建立连接,这台服务器会为这个客户端创建一个新的会话。**每个会话都会有一个超时时间,若服务器在超时时间内没有收到任何请求,则相应会话被视为过期。**一旦会话过期,就无法再重新打开,且任何与该会话相关的临时 znode 都会被删除。

通常来说,会话应该长期存在,而这需要由客户端来保证。客户端可以通过心跳方式(ping)来保持会话不过期。

ZooKeeper会员有四个属性

  • sessionID:会话 ID,唯一标识一个会话,每次客户端创建新的会话时,Zookeeper 都会为其分配一个全局唯一的 sessionID。
  • TimeOut:会话超时时间,客户端在构造 Zookeeper 实例时,会配置 sessionTimeout 参数用于指定会话的超时时间,Zookeeper 客户端向服务端发送这个超时时间后,服务端会根据自己的超时时间限制最终确定会话的超时时间。
  • TickTime:下次会话超时时间点,为了便于 Zookeeper 对会话实行”分桶策略”管理,同时为了高效低耗地实现会话的超时检查与清理,Zookeeper 会为每个会话标记一个下次会话超时时间点,其值大致等于当前时间加上 TimeOut。
  • isClosing:标记一个会话是否已经被关闭,当服务端检测到会话已经超时失效时,会将该会话的 isClosing 标记为”已关闭”,这样就能确保不再处理来自该会话的新请求

Zookeeper 的会话管理主要是通过 SessionTracker 来负责,其采用了分桶策略(将类似的会话放在同一区块中进行管理)进行管理,以便 Zookeeper 对会话进行不同区块的隔离处理以及同一区块的统一处理

应用

命名服务

在分布式系统中,通常需要一个全局唯一的名字,如生成全局唯一的订单号等,ZooKeeper 可以通过顺序节点的特性来生成全局唯一 ID,从而可以对分布式系统提供命名服务

配置管理

利用 ZooKeeper 的观察机制,可以将其作为一个高可用的配置存储器,允许分布式应用的参与者检索和更新配置文件,如果某一个配置文件修改之后,能够快速同步到各个节点,具体步骤如下

  • 可将配置信息写入ZooKeeper上的一个Znode
  • 各个客户端服务器监听这个Znode
  • 一旦Znode中的数据被修改,ZooKeeper将通知各个客户端服务器

分布式锁

可以通过 ZooKeeper 的临时节点和 Watcher 机制来实现分布式锁

集群管理

ZooKeeper 还能解决大多数分布式系统中的问题:

  • 如可以通过创建临时节点来建立心跳检测机制。如果分布式系统的某个服务节点宕机了,则其持有的会话会超时,此时该临时节点会被删除,相应的监听事件就会被触发。

  • 分布式系统的每个服务节点还可以将自己的节点状态写入临时节点,从而完成状态报告或节点工作进度汇报。

  • 通过监听机制,还能对分布式系统的服务节点进行动态上下线,从而实现服务的动态扩容。

选举Leader节点

分布式系统一个重要的模式就是主从模式 (Master/Salves),ZooKeeper 可以用于该模式下的 Matser 选举。可以让所有服务节点去竞争性地创建同一个 ZNode,由于 ZooKeeper 不能有路径相同的 ZNode,必然只有一个服务节点能够创建成功,这样该服务节点就可以成为 Master 节点。

队列管理

ZooKeeper 可以处理两种类型的队列:

  • 当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
  • 队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。

同步队列用 ZooKeeper 实现的实现思路如下:

创建一个父目录 /synchronizing,每个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 /synchronizing/member_i 的临时目录节点,然后每个成员获取 / synchronizing 目录的所有目录节点,也就是 member_i。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start。

注册中心

项目中,可以使用Zookeeper作为Dubbo的注册中心。

闲谈

感觉有帮助的同学还请点赞关注,这将对我是很大的鼓励~,公众号有自己开始总结的一系列文章,需要的小伙伴还请关注下个人公众号程序员fly呀,干货多多,湿货也不少(∩_∩)。

分布式Zookeeper-基础

巨人肩膀

  • 本文主要基于https://mp.weixin.qq.com/s/DwyPt5YZgqE0O0HYEC1ZMQ文章总结
  • https://juejin.cn/post/6844903677367418893#heading-4
  • https://mp.weixin.qq.com/s/bDBWbH_O4hgKp1vttGGIxA
  • https://blog.csdn.net/DavidSoCool/article/details/106016964
  • https://www.runoob.com/w3cnote/zookeeper-watcher.html
  • https://www.cnblogs.com/wuzhenzhao/p/9994450.html
上一篇:Zookeeper


下一篇:14. zookeeper部署