分布式学习手记06 - ZooKeeper 典型应用场景
ZooKeeper 是一个典型的发布/订阅模式的分布式数据管理与协调框架,提供丰富的数据节点类型和 Watcher 事件通知机制。
典型应用场景
提供分布式应用中都会涉及的核心功能,如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等。
数据发布/订阅
使用 ZooKeeper 实现配置中心功能,应用将配置信息发布到 ZooKeeper 集群部分节点上,供订阅者动态获取,实现配置信息的集中式管理和动态更新。
全局配置信息特点:数据量小、会动态变化、各节点共享同一副本
-
传统本地配置文件方式:
一旦集群规模变大,且配置信息变更逐渐频繁后,变更成本极大提高;
-
使用 ZooKeeper 集群保存配置信息:
客户端初始化阶段从 ZooKeeper 配置节点拉取原始数据,并在该节点注册一个数据变更的 Watcher 监听。一旦数据发生变更,所有订阅的客户端都会获取到变更通知,客户端重新拉取最新的配置数据。
负载均衡
负载均衡是分布式系统必备功能,将数据和服务根据系统中各对等节点的负载情况合理分配,保证集群高可用性。
本质上,DNS 系统类似一个超大规模的分布式映射表,因此,比照配置中心的实现, ZooKeeper 可以充当动态的自动化 DNS 服务中心。
如下,每个应用可以创建属于自己的数据节点作为域名配置的根节点,保存域名解析信息
/DDNS
|__app1
| |__server.app1.company1.com
|__app2
-
域名注册
服务提供者将自身业务的 IP、端口、域名信息发送给 Register,Register 将配置信息写入相应的 ZooKeeper 域名节点;
-
域名解析
系统 Dispatcher 收到服务消费者的解析请求后,从 ZooKeeper 上指定域名节点读取相应信息,通过负载均衡策略选出一个返回给消费者;
-
域名变更
当域名解析信息产生变更, ZooKeeper 向有订阅 Watcher 的客户端发送变更通知,应用收到通知后,重新获取最新数据;
-
域名探测
即域名健康检测,服务提供者定期向 Scanner 汇报状态,若 Scanner 超时未收到报告,则判定该服务提供者失效,清理 ZooKeeper 中该域名节点信息。
命名服务
Name Service 是分布式系统基本的公共服务之一,被命名的实体包括:集群中机器、提供的服务地址或远程对象等,这些被统称为名字(Name)。
通过使用命名服务,客户端应用可以通过指定名字来获取资源的实体、服务地址和提供者的信息等。
ZooKeeper 通过一套分布式全局唯一 ID 的分配机制,使得分布式环境中,上层应用可以通过全局唯一的 Name 定位和使用目标资源。
/jobs
|__type1
| |__job-00000001
| |__job-00000002
| |__...
|__type2
|__job-00000001
|__job-00000003
|__...
-
客户端根据业务类型,在指定分类下调用 ZooKeeper 的 create() 接口创建一个顺序节点 job-x;
-
ZooKeeper 自动以后缀形式为子节点添加序号,如 job-00000003;
-
客户端收到返回值,拼接上 type 字段,如 type2-job-00000003,即为全局唯一 ID。
分布式协调/通知
分布式环境中,需要一个协调者(Coordinator)控制整个系统的运行流程,从而将分布式协调的职责从应用中剥离,减少模块间耦合性,提高系统可扩展性。
1- 系统协调
/tasks
|__taskA
|__lastCommit
|__instances
| |__[HostnameX]-001
| |__[HostnameY]-002
|__status
-
任务注册
1- 任务执行节点,先在集群任务列表(tasks)创建子节点(taskA);
-
任务热备
多个 instance 应对业务执行过程中可能出现的故障:
2- 参与客户端都将自己的主机名写入 instances 数据节点;
3- ZooKeeper 为其分配临时顺序节点序号;
4- 根据 “小序号优先”策略 ,序号最小的子节点对应客户端进入 “RUNNING” 状态,其他客户端设为 “STANDBY” 状态;
-
热备切换
5- 状态标识后,”RUNNING” 客户端开始执行 taskA;
6- 若 “RUNNING” 客户端故障,与 ZooKeeper 断连,其对应临时数据节点被清除,订阅了 “子节点列表变更” Watcher 的其余 “STANDBY” 进程再次根据 “小序号优先” 开始新一轮 “RUNNING” 选举;
-
记录执行状态
执行 taskA 的 “RUNNING” 进程将运行上下文存储在 “lastCommit” 数据节点,热备切换后,新的执行进程读取上下文信息,继续执行 taskA;
-
控制台协调
控制组件将 taskA 相关信息以配置文件形式写入任务目录,以便所有任务参与者都能共享;
2- 分布式系统间的通信方式
-
心跳检测
不同机器在 ZooKeeper 一个指定节点下创建自己的临时子节点,以此临时节点判定各机器是否存活
互相之间不需直接关联,都从 ZooKeeper 节点上检测动态,减少耦合
-
进度汇报
临时节点既可以判断任务机器是否存活,还能存储任务进度信息
-
系统调度
节点上数据变更以事件形式推送给客户端,响应调度
集群管理
集群管理包括集群监控与操作控制,利用 ZooKeeper 两大特性(Watcher 监听和变更推送 & 会话失效临时节点自动清除)来实现
分布式日志收集系统
典型日志系统架构中,需要收集日志的源机器被分为多个组别,每组对应一个收集器。但运行过程中,源机器和收集器集群规模、配置、信息会发生变动。
如何快速、合理、动态地为每个收集器分配对应的日志源机器,其本质也是一种变更管理。
/logs
|__collector
|__host1
|__host2
|__ ...
|__hostN
|__status
-
注册收集器
在 ZooKeeper 上创建收集器的根节点 /logs/collector ,每个收集器启动时,在收集器节点下创建自己的节点 /logs/collector/hostN;
注:host 节点需为持久节点 —— 虽然临时节点可以利用自动清除特性标记收集器的存活性,但会丢失自有源机器列表。因此使用持久节点,再通过 /logs/collector/host/status 子节点表征收集器状态
-
任务分发
系统根据 collector 节点下子节点个数,将日志源机器分组,对应写入每一个收集器节点。这样每个收集器都能从自己的 host 节点上获取日志源机器列表,开始日志收集工作;
-
动态分配
系统中 /logs/collector 节点会随集群状态变化自动变更,当新的收集器加入或有故障下线,系统重新任务分发:
Master 选举
业务系统中的 Master 具有对分布式系统状态变更的决定权
-
优先在 ZooKeeper 上创建节点的机器,成为 Master;
-
其余机器则创建此节点的变更 Watcher,监控 Master 存活状况,准备参与下轮选举
分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式
常规利用关系型数据库排他性实现的进程互斥,数据库就成了该分布式系统的性能瓶颈
1- 排他锁
当前仅有一个事务获得锁,当锁释放后,其他等待获取锁的事务能收到通知
/exclusive_lock
|__lock
-
定义锁
ZooKeeper 上一个数据节点来表示一个锁 /exclusive_lock/lock
-
获取锁
所有客户端需要资源时都要试图创建临时节点 /exclusive_lock/lock ,但只有一个客户端能创建成功,即获取了锁;
其他未获取锁的客户端创建 /exclusive_lock/lock 临时节点的 Watcher,监听其变更
-
释放锁
当获取锁的客户端资源释放完毕或宕机,临时节点 /exclusive_lock/lock 被删除,其他客户端收到 Watcher 变更通知,重新进入“获取锁”流程
2- 共享锁
/shared_lock
|__host1-R-00001
|__host2-W-00002
|__...
|__hostN-R-0000N
-
定义锁
临时节点上包含客户端及其请求类型和系统排序
-
获取锁
客户端创建自己的临时节点 /shared_lock/hostN-?-0000N
-
操作类型及顺序
-
读请求:小于自己序号的都是读请求,则开始读取;若前面有写请求则等待 Watcher 变更通知
-
写请求: 若自己不是最小序号,则等待
-
-
释放锁
同上
-
改进
上述方法客户端获取全量节点变更,资源开销较大,当集群规模庞大时,可以仅订阅序号小于自己的最后一个节点即可
分布式队列
1- 先入先出 FIFO
/queue_fifo
|__host1-00001
|__host2-00002
|__...
|__hostN-0000N
类似全写操作的共享锁
-
获取 /queue_fifo 节点下所有子节点
-
确定自己节点序号顺序
-
若自己不是序号最小节点则等待,监听序号小于自己的最后一个节点
-
收到 Watcher 通知后,重复步骤 1
2- 分布式屏障 Barrier
系统等待全部元素聚集后,统一安排
创建一个节点作为聚集完成条件,所有客户端订阅该 Watcher