ZkClient 客户端
见 pdf 4.3
Curator 客户端
见 pdf 4.4
配置维护
分布式系统中,很多服务都是部署在集群中的,多台服务器中部署着完全相同的应用,起着完全相同的作用。集群中这些服务器的配置文件是完全相同的。
如果集群中服务器的配置文件需要进行修改,那么我们就需要逐台修改这些服务器中的配置文件。如果我们集群服务器比较少,那么这些修改还不是太麻烦,如果某些大型互联网公司 Hadoop 集群有数千台服务器,纯手工更改就显得不可行了。过多的人工参与会使得出错率大幅提升。
zk 可以通过 “发布/订阅模型” 实现对集群配置文件的管理与维护。
命名服务
可以为一定范围内的元素命名一个唯一标识,以与其他元素进行区分。在分布式系统中被命名的实体可以是集群中的主机、服务地址等。作用相当于 UUID,但一定不会重复。
原理 通过利用 zk 中顺序节点自动生成唯一编号的特点来实现命名服务,首先创建一组业务相关的节点,然后再在这些节点下创建顺序节点,此时的顺序节点的路径+名称即为生成的唯一标识。
DNS 服务器
zk 的 DNS 服务是命名服务的一种特殊用法。其对外表现是防止提供者
如果 app1 具有多个服务器名称,则可以在该应用节点下添加多个子节点。如果某个域名需要修改,则直接新增一个节点。
Master 选举
为了解决分布式系统中计算单元的单点问题,水平扩展计算单元的处理能力。
一般情况下,会在集群中选出一个 Master,用于协调集群中的其他 Slave 主机,对于 Slave 主机的状态具有决定权。
例如,读写分离集群,Master 处理写请求,Slave 处理读请求
对于复杂处理逻辑的系统,Master 负责处理复杂逻辑计算,然后将计算结果写入到一个中间存储系统(DB,或 DFS 等),Slave 负责从中间存储系统中读取结果,并向读请求进行相应。
广告推荐系统
需求:系统会根据用户画像,将用户归结为不同的种类。系统会为不同种类的用户推荐不同的广告。每个用户前端需要从广告推荐系统中获取到不同的广告ID
分析:这个向前端提供服务的广告推荐系统一定是一个集群,这样可以更加快速高效的为前端进行响应。推荐系统对于广告 ID 的计算是一个相对复杂且消耗 CPU 等资源的过程。如果让集群中每一台主机都可以执行这个计算逻辑的话,那么势必会造成资源浪费,且降低了响应效率。此时,可以只让一台主机去处理计算逻辑,然后将计算结果写入到某中间存储系统中,并通知集群中的其他主机从该中间存储系统中共享该计算结果,那么,这个运行计算逻辑的主机就是 Master,而其他主机则为 Slave
Master 选举
zk 中多个客户端对同一节点创建时,只有一个客户端可以成功的特性实现。
- 多个客户端同时发起对同一个临时节点进程创建的请求,最终只能有一个创建成功,这个创建成功的客户端就是 Master,其他客户端就是 Slave。
- 所有 Slave 都向这个临时节点的父节点注册一个子节点列表的 watcher 监听。
- 一旦该 Master 宕机,临时节点就会消失,zk 服务器就会向所有 Slave 发送事件,Slave在接收到事件后悔调用相应的回调,该回调会重新向这个父节点创建相应的临时子节点。谁创建成功,则谁就是新的 Master
MySQL 数据复制总线
分布式同步,分布式协调是分布式系统中不可缺少的环节,是将不同的分布式组件有机结合起来的关键。对于一个在多台机器上运行的应用而言,通常需要一个协调者来控制整个系统运行流程,例如执行的先后顺序或执行与不执行等。
MySQL 数据复制总线是一个实时数据复制框架,用于在不同的 MySQL 数据库实例间进行异步数据复制(表名、字段名不一样)。其核心部分由三部分组成:生产者、复制管道、消费者
从上述结构可以看出,replicator 存在单点问题,为了解决这个问题,需要为其设置多个热备主机。需要使用 zk 来做协调工作,即由 zk 来完成分布式同步工作。
MySQL 复制总线的工作步骤,总的来说分为三步:
复制任务注册
复制任务注册实际就是指不同的复制任务在 zk 中创建不同的 znode,即将复制任务注册到 zk 中。
在复制系统启动时,首先会在 zk 中创建相应的复制任务总节点 /mysql/
。
当 replicator 线程启动时,会在 /mysql/tasks/pay_record
,
replicator 热备
复制任务是由 replicator 主机完成的。为了防止 replicator 在复制过程中出现故障,replicator 采用热备容灾方案,将同一个复制任务部署到多个不同的 replicator 主机上,仅使一个处于 Running 状态,其他主机则处于 Standby 状态。当 Running 状态的主机出现故障,无法完成复制任务时,使某一个 Standby 状态主机转换为 Running 状态,继续完成复制任务。
- 在相应任务节点下创建一个用于管理 replicator 主机的节点
/mysql/tasks/pay_record/instances
- 在
/mysql/tasks/pay_record/instances
节点下为每一台 replicator 主机创建一个相应的临时顺序节点。 - 将序号最小的节点设置为 running,其他设置为 StandBy
- 在任务节点
/mysql/tasks/pay_record
下创建一个状态子节点,将当前 replicator 主机的状态写入到该节点的内容中。
主备切换
当 Running 态的主机出现宕机,则该主机对应的子节点马上就被删除了,然后再当前处于 Standby 状态中的 replicator中找到序号最小的子节点,然后将其状态马上修改为 Running ,完成切换。
集群管理
对于集群,总是希望能够随时获取以下信息
- 当前集群中各个主机的运行时状态
- 当前集群中主机的存活状况
分布式日志收集系统
分布式日志收集系统由四部分组成:日志源集群(产生大量日志)、日志收集器集群、Zookeeper集群、监控系统。
- 收集器的注册
在 zk 上创建各个收集器对应的节点,如 /logs/collector1
在系统启动时首先创建收集器根节点 /logs
,收集器逐个启动就会逐个在 logs
下创建对应的永久节点 /logs/collector1
- 任务分配
系统根据收集器的个数,将所有日志源集群主机分组,分别分配给各个收集器。
日志源主机会在为其分配的收集器节点下创建临时节点,一个日志源主机一个节点
- 状态收集
日志源主机状态,例如,日志源主机是否存活,已经产生多少日志等。
收集器运行状态,例如,收集器本身已经收集了多少字节的日志、当前 CPU、内存的使用状况等。
收集器/日志源主机会定时/实时将其自身状态写入到对应的节点内容
- 任务再分配 Rebalance
当出现收集器挂掉或扩容,就需要动态地进行日志收集任务再分配了,这个过程称为 Rebalance。
分配方案:
全局动态分配:
局部动态分配:首先定义好收集器的负载判别标准(热量、CPU消耗等等)。
分布式锁
分布式锁是控制分布式系统同步访问共享资源的一种方式。Zookeeper 可以实现分布式锁功能。根据用户操作类型的不同,可以分别为排它锁(写锁)和共享锁(读锁)。
实现
在 zk 上对于分布式锁的实现,使用的是类似于 /xs_lock/[hostname]-请求类型-序号
的临时顺序节点。
具体过程:
- 当一个客户端向某资源发出 R/W 操作请求时,该客户端首先会对
/xs_lock
节点注册子节点列表变更时间的 watcher 监听,随时监听子节点的变化情况。 - watcher 注册完毕后,其会在
/xs_lock
节点下创建一个读写操作的临时顺序节点。读写操作的顺序性就是通过这些子节点的顺序性提现的。 - 节点创建完后,其就会触发客户端的 watcher 回调(创建子节点的时候,子节点列表变了,则会触发回调),读取
/xs_lock
节点下的所有子节点列表,然后比较当前子节点对比其他子节点序号的大小关系,并根据读写操作的不同,执行不同的逻辑。
读请求:若没有比自己小的节点,或比自己小的节点都是读请求节点,则当前请求可以直接读取,若比自己小的节点中存在写请求,则当前请求等待。
写请求:若没有比自己更小的节点,则直接进行写操作。若发现有比自己小的节点,无论是读还是写节点,当前写操作都需要等待。 - 客户端操作完毕后,与 zk 的连接断开,则 zk 中该会话对应的节点消失。
注意:由于每次都注册监听子节点列表变化的 watcher 会导致大量的低效无用的操作,比如当001节点消失,host2345 都会收到回调,但host45 收到的回调显然什么事也做不了。
为了优化,在 zk 中创建相应的临时顺序节点后马上获取当前/xs_lock
的所有子节点列表,但任何客户端都不向xs_lock
注册用于监听子节点列表变化的 watcher。而是改为根据请求类型的不同向对其有影响的子节点注册 watcher
读请求:只需要 watcher 比其序号小的最后一个写节点
写请求:只需要 watcher 其序号-1的那个节点,无论读写节点。
分布式队列
提起分布式队列,马上可以想到 RabbitMQ、Kafka 等分布式消息队列中间件产品,zk 也可以实现简单的消息队列。
FIFO 队列
zk 实现 FIFO 队列的思路是:利用顺序节点的有序性,为每个数据在 zk 中创建一个相应的永久节点,然后每个节点都注册 watcher 监听。一个节点被消费,则会引发消费者消费下一个节点,直到消费完毕。
分布式屏障 Barrier 队列
Barrier队列是分布式系统中的一种同步协调器,规定了一个队列中的元素必须全部聚齐后才能执行后面的任务,否则一直等待。其常见于大规模分布式并行计算的应用场景中,最终合并计算需要基于很多并行计算的子结果来进行。
zk 对于 Barrier 的实现原理是,在 zk 中创建一个 /barrier
节点,其数据内容设置为屏障打开的阈值,即当其下的子节点数量达到该阈值后,app 才可进行最终的计算,否则一直等待。每一个并行计算完成,都会在 /barrier
下创建一个子节点,直到所有并行运算完成。