【分布式】Zookeeper应用场景

1、前言mysql

  在上一篇博客已经介绍了Zookeeper开源客户端的简单实用,本篇讲解Zookeeper的应用场景。sql

2、典型应用场景数据库

  Zookeeper是一个高可用的分布式数据管理和协调框架,而且可以很好的保证分布式环境中数据的一致性。在愈来愈多的分布式系统(Hadoop、HBase、Kafka)中,Zookeeper都做为核心组件使用。设计模式

  2.1 数据发布/订阅服务器

  数据发布/订阅系统,即配置中心。须要发布者将数据发布到Zookeeper的节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。发布/订阅通常有两种设计模式:推模式和拉模式,服务端主动将数据更新发送给全部订阅的客户端称为推模式;客户端主动请求获取最新数据称为拉模式,Zookeeper采用了推拉相结合的模式,客户端向服务端注册本身须要关注的节点,一旦该节点数据发生变动,那么服务端就会向相应的客户端推送Watcher事件通知,客户端接收到此通知后,主动到服务端获取最新的数据。网络

  若将配置信息存放到Zookeeper上进行集中管理,在一般状况下,应用在启动时会主动到Zookeeper服务端上进行一次配置信息的获取,同时,在指定节点上注册一个Watcher监听,这样在配置信息发生变动,服务端都会实时通知全部订阅的客户端,从而达到实时获取最新配置的目的。架构

  2.2 负载均衡并发

  负载均衡是一种至关常见的计算机网络技术,用来对多个计算机、网络链接、CPU、磁盘驱动或其余资源进行分配负载,以达到优化资源使用、最大化吞吐率、最小化响应时间和避免过载的目的。app

  使用Zookeeper实现动态DNS服务负载均衡

  · 域名配置,首先在Zookeeper上建立一个节点来进行域名配置,如DDNS/app1/server.app1.company1.com。

  · 域名解析,应用首先从域名节点中获取IP地址和端口的配置,进行自行解析。同时,应用程序还会在域名节点上注册一个数据变动Watcher监听,以便及时收到域名变动的通知。

  · 域名变动,若发生IP或端口号变动,此时须要进行域名变动操做,此时,只须要对指定的域名节点进行更新操做,Zookeeper就会向订阅的客户端发送这个事件通知,客户端以后就再次进行域名配置的获取。

  2.3 命名服务

  命名服务是分步实现系统中较为常见的一类场景,分布式系统中,被命名的实体一般能够是集群中的机器、提供的服务地址或远程对象等,经过命名服务,客户端能够根据指定名字来获取资源的实体、服务地址和提供者的信息。Zookeeper也可帮助应用系统经过资源引用的方式来实现对资源的定位和使用,广义上的命名服务的资源定位都不是真正意义上的实体资源,在分布式环境中,上层应用仅仅须要一个全局惟一的名字。Zookeeper能够实现一套分布式全局惟一ID的分配机制。

  经过调用Zookeeper节点建立的API接口就能够建立一个顺序节点,而且在API返回值中会返回这个节点的完整名字,利用此特性,能够生成全局ID,其步骤以下

  1. 客户端根据任务类型,在指定类型的任务下经过调用接口建立一个顺序节点,如"job-"。

  2. 建立完成后,会返回一个完整的节点名,如"job-00000001"。

  3. 客户端拼接type类型和返回值后,就能够做为全局惟一ID了,如"type2-job-00000001"。

  2.4 分布式协调/通知

  Zookeeper中特有的Watcher注册于异步通知机制,可以很好地实现分布式环境下不一样机器,甚至不一样系统之间的协调与通知,从而实现对数据变动的实时处理。一般的作法是不一样的客户端都对Zookeeper上的同一个数据节点进行Watcher注册,监听数据节点的变化(包括节点自己和子节点),若数据节点发生变化,那么全部订阅的客户端都可以接收到相应的Watcher通知,并做出相应处理。

  MySQL数据复制总线是一个实时的数据复制框架,用于在不一样的MySQL数据库实例之间进行异步数据复制和数据变化通知,整个系统由MySQL数据库集群、消息队列系统、任务管理监控平台、Zookeeper集群等组件共同构成的一个包含生产者、复制管道、数据消费等部分的数据总线系统。

  Zookeeper主要负责进行分布式协调工做,在具体的实现上,根据功能将数据复制组件划分为三个模块:Core(实现数据复制核心逻辑,将数据复制封装成管道,并抽象出生产者和消费者概念)、Server(启动和中止复制任务)、Monitor(监控任务的运行状态,若数据复制期间发生异常或出现故障则进行告警)

  每一个模块做为独立的进程运行在服务端,运行时的数据和配置信息均保存在Zookeeper上。

  ① 任务建立,Core进程启动时,首先向/mysql_replicator/tasks节点注册任务,如建立一个子节点/mysql_replicator/tasks/copy_hot/item,若注册过程当中发现该子节点已经存在,说明已经有其余Task机器注册了该任务,所以其自身不须要再建立该节点。

  ② 任务热备份,为了应对任务故障或者复制任务所在主机故障,复制组件采用"热备份"的容灾方式,即将同一个复制任务部署在不一样的主机上,主备任务机经过Zookeeper互相检测运行监控情况。不管在第一步是否建立了任务节点,每台机器都须要在/mysql_replicator/tasks/copy_hot_item/instrances节点上将本身的主机名注册上去,节点类型为临时顺序节点,在完成子节点建立后,天天任务机器均可以获取到本身建立的节点名及全部子节点列表,而后经过对比判断本身是不是全部子节点中序号最小的,如果,则将本身运行状态设置为RUNNING,其余机器设置为STANDBY,这种策略称为小序号优先策略。

  ③ 热备切换,完成运行状态的标示后,其中标记为RUNNING的客户端机器进行正常的数据复制,而标记为STANDBY的机器则进入待命状态,一旦RUNNING机器出现故障,那么全部标记为STANDBY的机器再次按照小序号优先策略来选出RUNNIG机器运行(STANDY机器须要在/mysql_replicator/tasks/copy_hot_item/instances节点上注册一个子节点列表变动监听,RUNNING机器宕机与Zookeeper断开链接后,对应的节点也会消失,因而全部客户端收到通知,进行新一轮选举)。

  ④ 记录执行状态,RUNNING机器须要将运行时的上下文状态保留给STANDBY机器。

  ⑤ 控制台协调,Server的主要工做就是进行任务控制,经过Zookeeper来对不一样任务进行控制和协调,Server会将每一个复制任务对应生产者的元数据及消费者的相关信息以配置的形式写入任务节点/mysql_replicator/tasks/copy_hot_item中去,以便该任务的全部任务机器都可以共享复制任务的配置。

  在上述热备份方案中,针对一个任务,都会至少分配两台任务机器来进行热备份(RUNNING和STANDBY、即主备机器),若须要MySQL实例须要进行数据复制,那么须要消耗太多机器。此时,须要使用冷备份方案,其对全部任务进行分组。

  Core进程被配置了所属组(Group),即若一个Core进程被标记了group1,那么在Core进程启动后,会到对应的Zookeeper group1节点下面获取全部的Task列表,假如找到任务"copy_hot_item"以后,就会遍历这个Task列表的instances节点,但凡尚未子节点,则建立一个临时的顺序节点如/mysql_replicator/task-groups/group1/copy_hot_item/instances/[Hostname]-1,固然,在这个过程当中,其余Core进程也会在这个instances节点下建立相似的子节点,按照"小序号优先"策略肯定RUNNING,不一样的是,其余Core进程会自动删除本身建立的子节点,而后遍历下一个Task节点,这样的过程称为冷备份扫描,这样,全部的Core进程在扫描周期内不断地对相应的Group下来的Task进行冷备份。

  在绝大多数分布式系统中,系统机器间的通讯无外乎心跳检测工做进度汇报系统调度

  ① 心跳检测,不一样机器间须要检测到彼此是否在正常运行,可使用Zookeeper实现机器间的心跳检测,基于其临时节点特性(临时节点的生存周期是客户端会话,客户端若立即后,其临时节点天然再也不存在),可让不一样机器都在Zookeeper的一个指定节点下建立临时子节点,不一样的机器之间能够根据这个临时子节点来判断对应的客户端机器是否存活。经过Zookeeper能够大大减小系统耦合。

  ② 工做进度汇报,一般任务被分发到不一样机器后,须要实时地将本身的任务执行进度汇报给分发系统,能够在Zookeeper上选择一个节点,每一个任务客户端都在这个节点下面建立临时子节点,这样不只能够判断机器是否存活,同时各个机器能够将本身的任务执行进度写到该临时节点中去,以便中心系统可以实时获取任务的执行进度。

  ③ 系统调度,Zookeeper可以实现以下系统调度模式:分布式系统由控制台和一些客户端系统两部分构成,控制台的职责就是须要将一些指令信息发送给全部的客户端,以控制他们进行相应的业务逻辑,后台管理人员在控制台上作一些操做,实际上就是修改Zookeeper上某些节点的数据,Zookeeper能够把数据变动以时间通知的形式发送给订阅客户端。

  2.5 集群管理

  Zookeeper的两大特性:

  · 客户端若是对Zookeeper的数据节点注册Watcher监听,那么当该数据及诶单内容或是其子节点列表发生变动时,Zookeeper服务器就会向订阅的客户端发送变动通知。

  · 对在Zookeeper上建立的临时节点,一旦客户端与服务器之间的会话失效,那么临时节点也会被自动删除。

  利用其两大特性,能够实现集群机器存活监控系统,若监控系统在/clusterServers节点上注册一个Watcher监听,那么但凡进行动态添加机器的操做,就会在/clusterServers节点下建立一个临时节点:/clusterServers/[Hostname],这样,监控系统就可以实时监测机器的变更状况。下面经过分布式日志收集系统的典型应用来学习Zookeeper如何实现集群管理。

  分布式日志收集系统的核心工做就是收集分布在不一样机器上的系统日志,在典型的日志系统架构设计中,整个日志系统会把全部须要收集的日志机器分为多个组别,每一个组别对应一个收集器,这个收集器其实就是一个后台机器,用于收集日志,对于大规模的分布式日志收集系统场景,一般须要解决两个问题:

  · 变化的日志源机器

  · 变化的收集器机器

  不管是日志源机器仍是收集器机器的变动,最终均可以归结为如何快速、合理、动态地为每一个收集器分配对应的日志源机器。使用Zookeeper的场景步骤以下

  ① 注册收集器机器,在Zookeeper上建立一个节点做为收集器的根节点,例如/logs/collector的收集器节点,每一个收集器机器启动时都会在收集器节点下建立本身的节点,如/logs/collector/[Hostname]

  ② 任务分发,全部收集器机器都建立完对应节点后,系统根据收集器节点下子节点的个数,将全部日志源机器分红对应的若干组,而后将分组后的机器列表分别写到这些收集器机器建立的子节点,如/logs/collector/host1上去。这样,收集器机器就可以根据本身对应的收集器节点上获取日志源机器列表,进而开始进行日志收集工做。

  ③ 状态汇报,完成任务分发后,机器随时会宕机,因此须要有一个收集器的状态汇报机制,每一个收集器机器上建立完节点后,还须要再对应子节点上建立一个状态子节点,如/logs/collector/host/status,每一个收集器机器都须要按期向该结点写入本身的状态信息,这可看作是心跳检测机制,一般收集器机器都会写入日志收集状态信息,日志系统经过判断状态子节点最后的更新时间来肯定收集器机器是否存活。

  ④ 动态分配,若收集器机器宕机,则须要动态进行收集任务的分配,收集系统运行过程当中关注/logs/collector节点下全部子节点的变动,一旦有机器中止汇报或有新机器加入,就开始进行任务的从新分配,此时一般由两种作法:

  · 全局动态分配,当收集器机器宕机或有新的机器加入,系统根据新的收集器机器列表,当即对全部的日志源机器从新进行一次分组,而后将其分配给剩下的收集器机器。

  · 局部动态分配,每一个收集器机器在汇报本身日志收集状态的同时,也会把本身的负载汇报上去,若是一个机器宕机了,那么日志系统就会把以前分配给这个机器的任务从新分配到那些负载较低的机器,一样,若是有新机器加入,会从那些负载高的机器上转移一部分任务给新机器。

  上述步骤已经完整的说明了整个日志收集系统的工做流程,其中有两点注意事项。

  ①节点类型,在/logs/collector节点下建立临时节点能够很好的判断机器是否存活,可是,若机器挂了,其节点会被删除,记录在节点上的日志源机器列表也被清除,因此须要选择持久节点来标识每一台机器,同时在节点下分别建立/logs/collector/[Hostname]/status节点来表征每个收集器机器的状态,这样,既能实现对全部机器的监控,同时机器挂掉后,依然可以将分配任务还原。

  日志系统节点监听,若采用Watcher机制,那么通知的消息量的网络开销很是大,须要采用日志系统主动轮询收集器节点的策略,这样能够节省网络流量,可是存在必定的延时。

  2.6 Master选举

  在分布式系统中,Master每每用来协调集群中其余系统单元,具备对分布式系统状态变动的决定权,如在读写分离的应用场景中,客户端的写请求每每是由Master来处理,或者其经常处理一些复杂的逻辑并将处理结果同步给其余系统单元。利用Zookeeper的强一致性,可以很好地保证在分布式高并发状况下节点的建立必定可以保证全局惟一性,即Zookeeper将会保证客户端没法重复建立一个已经存在的数据节点。

  首先建立/master_election/2016-11-12节点,客户端集群天天会定时往该节点下建立临时节点,如/master_election/2016-11-12/binding,这个过程当中,只有一个客户端可以成功建立,此时其变成master,其余节点都会在节点/master_election/2016-11-12上注册一个子节点变动的Watcher,用于监控当前的Master机器是否存活,一旦发现当前Master挂了,其他客户端将会从新进行Master选举。

  2.7 分布式锁

  分布式锁用于控制分布式系统之间同步访问共享资源的一种方式,能够保证不一样系统访问一个或一组资源时的一致性,主要分为排它锁和共享锁。

  排它锁又称为写锁或独占锁,若事务T1对数据对象O1加上了排它锁,那么在整个加锁期间,只容许事务T1对O1进行读取和更新操做,其余任何事务都不能再对这个数据对象进行任何类型的操做,直到T1释放了排它锁。

  ① 获取锁,在须要获取排它锁时,全部客户端经过调用接口,在/exclusive_lock节点下建立临时子节点/exclusive_lock/lock。Zookeeper能够保证只有一个客户端可以建立成功,没有成功的客户端须要注册/exclusive_lock节点监听。

  ② 释放锁,当获取锁的客户端宕机或者正常完成业务逻辑都会致使临时节点的删除,此时,全部在/exclusive_lock节点上注册监听的客户端都会收到通知,能够从新发起分布式锁获取。

  共享锁又称为读锁,若事务T1对数据对象O1加上共享锁,那么当前事务只能对O1进行读取操做,其余事务也只能对这个数据对象加共享锁,直到该数据对象上的全部共享锁都被释放。

  ① 获取锁,在须要获取共享锁时,全部客户端都会到/shared_lock下面建立一个临时顺序节点,若是是读请求,那么就建立例如/shared_lock/host1-R-00000001的节点,若是是写请求,那么就建立例如/shared_lock/host2-W-00000002的节点。

  ② 判断读写顺序,不一样事务能够同时对一个数据对象进行读写操做,而更新操做必须在当前没有任何事务进行读写状况下进行,经过Zookeeper来肯定分布式读写顺序,大体分为四步。

    1. 建立完节点后,获取/shared_lock节点下全部子节点,并对该节点变动注册监听。

    2. 肯定本身的节点序号在全部子节点中的顺序。

    3. 对于读请求:若没有比本身序号小的子节点或全部比本身序号小的子节点都是读请求,那么代表本身已经成功获取到共享锁,同时开始执行读取逻辑,如有写请求,则须要等待。对于写请求:若本身不是序号最小的子节点,那么须要等待。

    4. 接收到Watcher通知后,重复步骤1。

  ③ 释放锁,其释放锁的流程与独占锁一致。

  上述共享锁的实现方案,能够知足通常分布式集群竞争锁的需求,可是若是机器规模扩大会出现一些问题,下面着重分析判断读写顺序的步骤3。

  针对如上图所示的状况进行分析

  1. host1首先进行读操做,完成后将节点/shared_lock/host1-R-00000001删除。

  2. 余下4台机器均收到这个节点移除的通知,而后从新从/shared_lock节点上获取一份新的子节点列表。

  3. 每台机器判断本身的读写顺序,其中host2检测到本身序号最小,因而进行写操做,余下的机器则继续等待。

  4. 继续...

  能够看到,host1客户端在移除本身的共享锁后,Zookeeper发送了子节点更变Watcher通知给全部机器,然而除了给host2产生影响外,对其余机器没有任何做用。大量的Watcher通知和子节点列表获取两个操做会重复运行,这样会形成系能鞥影响和网络开销,更为严重的是,若是同一时间有多个节点对应的客户端完成事务或事务中断引发节点小时,Zookeeper服务器就会在短期内向其余全部客户端发送大量的事件通知,这就是所谓的羊群效应

  能够有以下改动来避免羊群效应。

  1. 客户端调用create接口常见相似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点。

  2. 客户端调用getChildren接口获取全部已经建立的子节点列表(不注册任何Watcher)。

  3. 若是没法获取共享锁,就调用exist接口来对比本身小的节点注册Watcher。对于读请求:向比本身序号小的最后一个写请求节点注册Watcher监听。对于写请求:向比本身序号小的最后一个节点注册Watcher监听。

  4. 等待Watcher通知,继续进入步骤2。

  此方案改动主要在于:每一个锁竞争者,只须要关注/shared_lock节点下序号比本身小的那个节点是否存在便可。

  2.8 分布式队列

  分布式队列能够简单分为先入先出队列模型等待队列元素汇集后统一安排处理执行的Barrier模型

  ① FIFO先入先出,先进入队列的请求操做先完成后,才会开始处理后面的请求。FIFO队列就相似于全写的共享模型,全部客户端都会到/queue_fifo这个节点下建立一个临时节点,如/queue_fifo/host1-00000001。

  建立完节点后,按照以下步骤执行。

  1. 经过调用getChildren接口来获取/queue_fifo节点的全部子节点,即获取队列中全部的元素。

  2. 肯定本身的节点序号在全部子节点中的顺序。

  3. 若是本身的序号不是最小,那么须要等待,同时向比本身序号小的最后一个节点注册Watcher监听。

  4. 接收到Watcher通知后,重复步骤1。

  ② Barrier分布式屏障,最终的合并计算须要基于不少并行计算的子结果来进行,开始时,/queue_barrier节点已经默认存在,而且将结点数据内容赋值为数字n来表明Barrier值,以后,全部客户端都会到/queue_barrier节点下建立一个临时节点,例如/queue_barrier/host1。

  建立完节点后,按照以下步骤执行。

  1. 经过调用getData接口获取/queue_barrier节点的数据内容,如10。

  2. 经过调用getChildren接口获取/queue_barrier节点下的全部子节点,同时注册对子节点变动的Watcher监听。

  3. 统计子节点的个数。

  4. 若是子节点个数还不足10个,那么须要等待。

  5. 接受到Wacher通知后,重复步骤3。

3、总结

  本篇博客讲解了如何利用Zookeeper的特性来完成典型应用,展现了Zookeeper在解决分布式问题上的强大做用,基于Zookeeper对分布式数据一致性的保证及其特性,开发人员可以构建出本身的分布式系统。也谢谢各位园友的观看~

相关文章
相关标签/搜索