Zookeeper 基本概念及应用场景介绍

195 阅读9分钟

前言:

想学习Zookeeper不可避免的会遇到一些基本概念,下面就一些基本概念的内容进行介绍。

一,Zookeeper介绍

Zookeeper 是一个开源的分布式协调服务。是分布式系统的协同调度服务中心。

说重点:zookeeper 是一个分布式的协调中心,可以实现的功能有很多,比如服务的注册与发现、集群管理,甚至是负载均衡都可以。

不用觉得 zookeeper 很难,zookeeper 说白了其实就两个功能:

  • 存储
  • 通知

存储,即可以存储数据。通知,即事件监听,客户端向服务端注册自己需要关注的节点,一旦该节点数据发生变更,服务端就会向客户端发送一个事件通知。

记住这两个功能,后面会反复提及,接下来开始介绍zookeeper。


在介绍 zookeeper 的基本概念之前,我们先来看一下 zookeeper 的工作流程,不用记住,只需要过一遍心里有个对 zookeeper 的大概印象即可。

前置知识:Zookeeper 服务器有三种角色,leader(为客户端提供读和写),fllower 和 Observer(为客户端提供读的功能,区别是 observer 不参与投票(投票是 Zookeeper 重要的工作机制,这里先留一个印象)。)

如图所示,一般生产环境都是集群模式,避免单个挂掉 zookeeper 服务就挂了。既然是集群模式,那集群中各个节点的数据一致性就成了问题,而 zookeeper 的工作原理恰好能保证了他集群内节点的数据一致性。具体流程如下:

  1. 客户端一般有两种请求,读和写。上图,fllower 只处理读请求,leader 只处理写请求。fllower 如果接收到写请求会转发给 leader。

  2. leader 接收到写请求,把这个写请求当成一个事务,然后包装成一个提议(proposal),发送给所有的 fllower这个提议,说:“我要改数据了,你们怎么看”。

  3. fllower 接收到 leader 发送的提议,如果本身没问题,同意修改数据,那就发送给 leader 确认 ack:“我们没问题”。

  4. leader 收到半数以上的 fllower 的确认 ack 后,就先提交自身的事务,然后给所有的 fllower 发送 commit 消息告诉他们:“既然没问题我就提交事务了,你们也抓紧提交吧,才能保证数一致。”

  5. fllower 收到 commit 消息后,就修改提交自身事务,保证数据一致,再回复一个 ack 给 leader 表示提交ok。

上述流程就是 zookeeper 最重要的一个工作流程,这里先留个印象,有了上述铺垫,接下来学习 zookeeper 会轻松很多。接着我们就开始介绍 zookeeper 的基本概念。

基本概念:

  • 集群角色:颠覆了传统主备模式的Master和Slave角色,而是引入了 Leader,Follower,Observer 三种角色。Leader 负责读写,Follower 和 Observer 负责读,区别是 Observer 不参与投票。
  • 会话:客户端启动时,就会和 Zookeeper 对外的服务端口[2181]建立一个TCP长连接,这就是会话,客户端通过这个会话来和服务端进行通信。
  • 数据节点(Znode):Zookeeper 的结构是类似文件树的结构。
  • 版本:Zookeeper 的每个 Znode 上都会存储数据,对于每个 Znode,Zookeeper 都会维护一个叫做 Stat 的数据结构,Stat 记录了这个节点的版本信息。
  • Watcher(时间监听器):Zookeeper 允许用户在指定节点上注册一些监听器监听特定事件,事件发生时通知感兴趣的客户端。
  • ACL(Access Constrol Lists):权限控制的策略。

还记得上面两个最基本的功能存储和通知吗?

存储功能落实到 Zookeeper 上的实现就是一个个的节点,称为 ZNode,每个 ZNode 由两部分组成(data + state)那 ZNode 节点到底长什么样子呢? 以下示例是查询节点 zk-test 的信息,其中第一行是数据,后面的信息都是状态信息。

# 其中第一行 321 是数据
# 后面是 state
[zk: localhost:2181(CONNECTED) 15] get -s /zk-test 
321
cZxid = 0x2
ctime = Wed Jun 28 17:12:05 CST 2023
mZxid = 0x3
mtime = Wed Jun 28 17:17:31 CST 2023
pZxid = 0x2
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0

通知功能,这里就得说一下 Zookeeper 的 Watcher 机制:

简单说,Watcher 就是客户端向服务端的一个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端向指定客户端发送一个事件通知来实现通知功能,然后客户端根据这个通知做出改变。

工作机制

  1. 客户端注册 watcher
  2. 服务端处理 watcher
  3. 客户端回调 watcher

Zookeeper 中还有一个比较重要的概念 ZXID,即事务ID

⾸先,先了解,事务是对物理和抽象的应⽤状态上的操作集合。往往在现在的概念中,狭义上的事务通常指的是数据库事务,⼀般包含了⼀系列对数据库有序的读写操作,这些数据库事务具有所谓的ACID特 性,即原⼦性(Atomic)、⼀致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

而在 ZooKeeper 中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,⼀般包括数据节点创建与删除、数据节点内容更新等操作。对于每⼀个事务请求,ZooKeeper 都会为其分配⼀个全局唯⼀的事务ID,⽤ ZXID 来表示,通常是⼀个 64 位的数字。每⼀个 ZXID 对应⼀次更新操作,从这些 ZXID 中可以间接地识别出 ZooKeeper 处理这些更新操作请求的全局顺序。


说完了这些基本概念,如果你想了解 Zookeeper 的安装和使用,可以看我另一篇文章:Zookeeper 安装与简单使用,接下来说一下 Zookeeper 能有哪些应用场景。

二,zookeeper的应用场景

数据发布/订阅

Zookeeper 根据 watcher 的使用来实现服务端和客户端之间的推 push 和拉 pull 。

ZooKeeper 采用的是推拉相结合的⽅式:客户端向服务端注册⾃⼰需要关注的节点,⼀旦该节点的数据 发生变更,那么服务端就会向相应的客户端发送 Watcher 事件通知,客户端接收到这个消息通知之后, 需要主动到服务端获取最新的数据。

命名服务

给分布式中的实体命名,命名全局唯一,相当于一个唯一ID,可以采用 zookeeper 的顺序节点(什么是顺序节点?跳转 Zookeeper 安装与简单使用 使用 Ctrl + f 查看)来实现。用 create() 创建顺序节点会递增,这个递增名称就可以作为命名。

集群管理

包含集群监控和集群控制,比如一个日志采集系统,可以利用 Zookeeper 的节点这种机制,一个节点当成收集器,节点下有很多个日志源节点待收集,watcher 又能很好的监控。以此实现整体的日志节点的整体管控。

Master选举

分布式系统中常见的 master 选举怎么实现呢?

一是可以利用数据库的主键唯一性,多个机器去创建主键id,只有一个能创建成功,创建成功就是 master。

二是使用 Zookeeper 的特性,Zookeeper 会保证客户端无法重复创建一个已经存在的节点,即 Zookeeper 的强一致性,可以用多个客户端来创建,谁创建成功就是 master,其他没创建成功的在父节点上注册 watcher,一旦 master 挂掉就重新选举。

分布式锁

(科普一下):实现方式一般有三种:

  1. 关系型数据库实现分布式锁,利用字段的唯一性约束,要锁住资源是,多个线程同时创建一条数据,用相同的字段名,只有一条会创建成功,就获得锁,处理完逻辑,释放锁时就删除数据库的这条记录。
  2. 基于缓存实现分布式锁。
  3. 基于Zookeeper实现分布式锁。
  • Zookeeper-排他锁:所有客户端在一个节点下创建一个 lock 节点,名字相同,只有一个创建成功,即获得锁。

  • zookeeper-共享锁:在 Zookeeper 中实现共享锁的思路。(后续另开一篇文章细细嗦嗦

  • 羊群效应:如果客户端太多,Zookeeper 在字节列表发生删除时,会发送大量通知给客户端,影响性能。

分布式队列

FIFO队列:Zookeeper 怎么实现先进先出队列呢? 同一节点下创建顺序节点即可,客户端判断自己的顺序节点是不是当前最小的,是就执行。

Barrier 分布式屏障:即指定队列必须达到一定的数量后再同一安排进行,哪 Zookeeper 怎么实现呢?在一个节点的数据内容存在一个数量,多客户端在这个节点下创建子节点,根据watcher读取节点编号,当子节点的数量达到这个数量时才进行安排执行(getData()办法可以获取节点数据内容)。

负载均衡

Zookeeper 也是可以实现负载均衡的:简直无所不能。

这里重点说一下 Zookeeper 的负载均衡的实现思想:

Zookeeper 上面会注册有多个服务端,客户端获取 Zookeeper 上的服务节点数量,本地记录一个 index 脚标变量,记录一下当前调用的是第几个序号发服务器,下次加一,进行调用即可实现最基本的轮询的负载均衡。

总结

Zookeeper 这么多应用场景是需要我们代码实现的,之所以能有这么多应用场景,就是因为 Zookeeper 提供了 存储 + 通知 的功能,以及在分布式环境下能保证各个节点数据一致的特性。