一、配置维护
配置维护也就是配置中心,目前市面上比较常见的配置中心(注册中心)有spring cloud config、Nacos、Apollo等
zk可以通过发布订阅模式实现对集群文件的管理和维护。发布订阅模式分为推模式和拉模式,而zk的发布订阅模式采用的是推拉结合的方式实现的。
实现原理:
首先应用要向zk服务端注册一个watcher监听
然后当有人修改配置信息时,zk会推送watcher事件给应用
最后应用主动再去zk服务器拉去需要更新的数据
二、命名服务
服务命名指的是可以为一定范围内的元素制定一个唯一的名称,可以用来进行区分。在分布式的项目中,一般被命名的都是集群中的主机,服务地址等信息
实现原理:
其是使用了zk的节点路径不能重复的特点来实现的服务命名,同时,也可以配上顺序节点的有序性来体现唯一标识的顺序性。
三、集群管理
对于集群,我们总是希望能够随时获取到当前集群中各个主机的运行时状态、当前集群中主机的存活状况等信息。通过 zk 可以实现对集群的随时监控。
其基本原理是使用zk的临时节电来进行监控。如上图所示,主机向zk注册临时节点,监控系统注册监听集群下的临时节点,从而获取集群中服务的状态等信息。
以分布式日志收集系统为例,该系统分为四部分,分别是:产生日志的业务系统组成的日志源集群、收集日志的日志采集集群、zk集群、监控管理集群
如上图所示,在/logs节点下创建日志收集集群的各个服务器节点,在其节点下,是日志源项目的节点,这样查看每一个收集日志节点下还有哪些临时借点,就可以知道有哪些应用处于健康的日志收集状态。
同时可以看到,在日志手机节点collector1限免还有个collector1-0节点,这是因为日志收集节点是持久化节点,不会因为断开会话就删除节点,因此考虑在其节点下新增一个自己使用的临时节点,用来确认日志收集集群的状态信息。
因此,当收集日志的集群注册时,其是注册了两个节点,一个是持久化的collector节点,一个是collector下面的临时节点collector1-0。而日志源集群注册时,就只需要在对应的collector下创建一个临时节点即可。
任务再分配:当日志收集器挂掉或扩容时,就需要动态的进行日志收集再分配,这个过程称之为Rebalance。
四、DNS服务
zk 的 DNS 服务的功能主要是实现消费者与提供者的解耦合,防止提供者的单点问题,实现对提供者的负载均衡。
假设提供者应用程序 app1 与 app2 分别用于提供 service1 与 service2 两种服务,现要将其注册到 zk 中,具体的实现步骤如下图所示。
具体实现步骤:
Step1:在 zk 上为当前 DNS 功能创建一个根节点,例如/DNS。
Step2:以该提供者的服务名称为名在应用根节点下创建子节点,该节点即为域名节点,例如/DNS/ service1。
Step3:为域名节点添加数据内容,数据内容为当前服务的所有提供者主机地址集合,即多个提供者地址间使用逗号分隔。
具有状态收集功能的DNS实现原理:
如上图所示,除了具有DNS解析功能外,期还有一个重要的功能就是状态同步:一个状态收集器来定时收集集群中各个服务节点的状态,并将服务状态存储在对应的服务节点上,但是这个过程就需要我们自己编写代码来进行实现了。
阿里的Dubbo就是使用的zk进行的DNS域名解析服务。
五、Master选举
集群是分布式系统中不可或却的组成部分,是为了解决分布式系统中计算单元的单点问题,水平扩展计算单元的处理能力的一种解决方案。
一般情况下,会在群集中选举出一个 Master,用于协调集群中的其它 Slave 主机,对于Slave 主机的状态具有决定权。
使用zk的master选举是利用了zk中多个客户端对同一节点创建时,只有一个客户端可以成功的特性实现。
具体来说,由三步完成:
Step1:多个客户端同时发起对同一临时节点/master-election/master 进行创建的请求,最终只能有一个客户端成功。这个成功的客户端主机就是 Master,其它客户端就是 Slave。
Step2:让 Slave 都向这个临时节点的父节点/master-election 注册一个子节点列表的watcher 监听。
Step3:一旦该 Master 宕机,临时节点就会消失,zk 服务器就会向所有 Slave 发送子节点变更事件,Slave 在接收到事件后会调用相应的回调方法,该回调方法会重新向这个父节点创建相应的临时子节点。谁创建成功,谁就是新的 Master。
六、分布式同步
分布式同步,也称为分布式协调,是分布式系统中不可缺少的环节,是将不同的分布式组件有机结合起来的关键。对于一个在多台机器上运行的应用而言,通常需要一个协调者来控制整个系统的运行流程,例如执行的先后顺序,或执行与不执行等。
以mysql复制总线为例:
下图是mysql的消息复制总线的一个示例图,其由三部分组成:生产者、消费者和复制管道,其中复制管道是用来做mysql复制的关键,但是其存在单点问题,解决这个单点问题,就需要使用到zk的分布式同步。
强调一下:这里的同步不是指的同一个集群内的mysql主从复制,而是不同集群间的数据同步。其主要作用是保证复制管道replicator不出现单点问题而造成同步失败。
使用zk来处理mysql复制总线如下图所示:
MySQL 复制总线的工作步骤,总的来说分为三步:
1、复制任务注册
复制任务注册实际就是指不同的复制任务在 zk 中创建不同的 znode,即将复制任务注册到 zk 中。
2、replicator 热备
复制任务是由 replicator 主机完成的。为了防止 replicator 在复制过程中出现故障,replicator 采用热备容灾方案,即将同一个复制任务部署到多个不同的 replicator 主机上,但仅使一个处于 RUNNING 状态,而其它的主机则处于 STANDBY 状态。当 RUNNING 状态的主机出现故障,无法完成复制任务时,使某一个 STANDBY 状态主机转换为 RUNNING 状态,继续完成复制任务。
3、主备切换
当 RUNNING 态的主机出现宕机,则该主机对应的子节点马上就被删除了,然后在当前处于 STANDBY 状态中的 replicator 中找到序号最小的子节点,然后将其状态马上修改为RUNNING,完成“主备切换”。
七、分布式锁
分布式锁是控制分布式系统同步访问共享资源的一种方式。Zookeeper 可以实现分布式锁功能。根据用户操作类型的不同,可以分为排他锁(写锁)与共享锁(读锁)。
在 zk 上对于分布式锁的实现,使用的是类似于“/xs_lock/[hostname]-请求类型-序号”的临时顺序节点。当客户端发出读写请求时会在 zk 中创建不同的节点。根据读写操作的不同及当前节点与之前节点的序号关系来执行不同的逻辑。
具体实现步骤:
Step1:当一个客户端向某资源发出读/写请求时,若发现其为第一个请求,则首先会在 zk中创建一个根节点。若节点已经存在,则无需创建。
Step2:根节点已经存在了,客户端在根节点上注册子节点列表变更的 watcher 监听。
Step3:watcher 注册完毕后,其会在根节点下创建一个读/写操作的临时顺序节点。读写操作创建的节点名称是不同的
Step4:节点创建完毕后,其就会马上触发客户端的 watcher 回调的执行。回调方法首先会将子节点列表读取,然后会查看序号比自己小的节点,并根据读写操作的不同,执行不同的逻辑。
读操作请求:若没有比自己序号小的子节点,或所有比自己小的子节点都是读操作请求,则表示自己可以开始读数据了;若比自己序号小的节点中存在有写操作请求,则当前客户端不能对数据进行读操作,而是进入等待状态,等待前面的写操作完成。
写操作请求:若没有比自己小的子节点,则表示自己可以开始对数据进行更新了;若发现还存在比自己小的子节点,这此节点无论是读操作请求还是写操作请求,当前写操作请求都需要进行等待状态,等待前面所有操作完成。
Step5:客户端读写操作完毕,其与 zk 的连接断开,则 zk 中该会话对应的节点消失。
分布式锁的改进:
前面的实现方式存在“惊群效应”,为了解决其所带来的性能下降,可以对前述分布式锁的实现进行改进。
由于一个操作而引发了大量的低效或无用的操作的执行,这种情况称为惊群效应。当客户端请求发出后,在 zk 中创建相应的临时顺序节点后马上获取当前的/xs_lock 的所有子节点列表,但任何客户端都不向/xs_lock 注册用于监听子节点列表变化的 watcher。而是改为根据请求类型的不同向“对其有影响的”子节点注册 watcher。
读操作请求:若其前面都是读请求节点,则可以直接开始读操作;若其前面有写操作请求节点,其只需要向序号小于自己的最后一个写操作请求节点注册“节点删除”watcher,然后等待。
写操作请求:若查看到自己的序号是最小的节点,则可以直接开始写操作;若发现还有 比自己更小的节点,则其只需向序号小于自己的最后一个节点注册“节点删除”watcher,然后等待。
八、分布式队列
zk可以实现分布式队列,但是不要与RabbitMQ、Kafka等专门的消息队列对比,这没有可比性,只是说zk可以实现这个功能而已。
zk可以实现先进先出的分布式队列和分布式屏障Barrier队列。
1、先进先出FIFO队列
zk 实现 FIFO 队列的思路是:利用顺序节点的有序性,为每个数据在 zk 中都创建一个相应的节点。然后为每个节点都注册 watcher 监听。一个节点被消费,则会引发消费者消费下一个节点,直到消费完毕。
2、分布式凭证Barrier队列
Barrier,屏障、障碍物。Barrier 队列是分布式系统中的一种同步协调器,规定了一个队列中的元素必须全部聚齐后才能继续执行后面的任务,否则一直等待。其常见于大规模分布式并行计算的应用场景中:最终的合并计算需要基于很多并行计算的子结果来进行。
zk 对于 Barrier 的实现原理是,在 zk 中创建一个/barrier 节点,其数据内容设置为屏障打开的阈值,即当其下的子节点数量达到该阈值后,app 才可进行最终的计算,否则一直等待。每一个并行运算完成,都会在/barrier 下创建一个子节点,直到所有并行运算完成。