系列文章目录
认识 Zookeeper -基本概念,组成和功能_小王师傅66的博客-CSDN博客
zookeeper-集群-选举机制_小王师傅66的博客-CSDN博客
ZooKeeper-集群-ZAB协议与数据同步_小王师傅66的博客-CSDN博客
Zookeeper-应用-分布式锁以及和Redis实现对比_小王师傅66的博客-CSDN博客
前言
在前面的几篇文章中,我们讲解了ZooKeeper的组成,基本功能,集群选举,ZAB协议和数据一致性。所有的设计都因应用场景而生,不结合场景的方案都是耍流氓。在今天的文章中,我们将要去了解ZK的应用,加深对ZK的认识。
1、数据发布/订阅
数据发布/订阅(Publish/Subscribe)系统,即所谓的配置中心,顾名思义就是发布者将数据发布到ZooKeeper的一个或一系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
在集群机器规模不大,配置变更不是特别频繁的情况下,用本地配置文件和内存变量实现配置管理都是可以的。但是当机器规模变大,且配置信息变更越发频繁后,我们就希望有一种能做到全局配置信息变更,且成本小的分布式化解决方案,ZooKeeper实现了。
ZooKeeper采用的是推拉结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知之后,需要主动到服务端获取最新的数据。
2、负载均衡
在分布式系统中,负载均衡更是一种普遍的技术,基本上每一个分布式系统都需要使用负载均衡。分布式系统具有对等性,为了保证系统的高可用性,通常采用副本的方式来对数据和服务进行部署。而对于消费者而言,则需要在这些对等的服务提供方中选择一个来执行相关的业务逻辑,其中比较典型的就是DNS服务。
当应用的机器规模在一定范围内,并且域名的变更不是特别频繁时,本地HOST绑定是非常高效且简单的方式。然而一旦机器规模变大后,就常常会碰到这样的情况:我们在应用上线的时候,需要在应用的每台机器上去绑定域名,但是在机器规模相当庞大的情况下,这种做法就相当不方便。另外,如果想要临时更新域名,还需要到每个机器上去逐个进行变更,要消耗大量时间,因此
完全无法保证实时性。下面介绍一种基于ZooKeeper实现的动态DNS方案。
在传统的DNS解析中,我们都不需要关心域名的解析过程,所有这些工作都交给了操作系统的域名和IP地址映射机制(本地HOST绑定)或是专门的域名解析服务器(由域名注册服务商提供)。因此,在这点上,DDNS方案和传统的域名解析有很大的区别在DDNS中,域名的解析过程都是由每一个应用自己负责的。通常应用都会首先从域名节点中获取一份IP 地址和端口的配置,进行自行解析。同时,每个应用还会在域名节点上注册一个数据变更Watcher监听,以便及时收到域名变更的通知。
如上图,我们设置app2的域名是:server.app2.company.com,域名对应的IP和端口的配置是192.168.0.1:8080,我们把配置信息存储到/DDNS/app2/server.app2.company.com节点上,同时使应用在/DDNS/app2/server.app2.company.com上注册一个数据变更的Watcher。这时候我修改配置信息,添加一个IP和端口:192.168.0.2:8080,此时应用立刻感知到节点内容发生变化,于是拉取并解析节点信息,拿到新的配置信息,将请求发送到192.168.0.1:8080 和192.168.0.2:8080,这样就实现了一个动态的负载均衡。
3、命名服务
命名服务(NameService)也是分布式系统中比较常见的一类场景,在《Java网络高级编程》一书中提到,命名服务是分布式系统最基本的公共服务之一。在分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等一这些我们都可以统称它们为名字(Name),其中较为常见的就是一些分布式服务框架(如RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据指定名字来获取资源的实体、服务地址和提供者的信息等。
以全局唯一ID命名为例。说到全局唯一ID,大家可能会想到UUID。一个标准的UUID是一个包含32位字符和4个短线的字符串,例如:“e70f1357-f260-46ff-a32d-53a086c57ade99”,能够简便地保证分布式环境的唯一性。但UUID生成的字符串过长,存储需要花费很长的时间;同时,从这个字符串上,开发人员很难从字面上看出表达的含义,这将会大大影响问题排查和开发调试的效率。为什么这样说呢,我们以分布式系统-商城系统-订单模块举个例子。系统中的每笔订单都需要有唯一性,如果在满足订单编号唯一性的同时,还能从订单编号上得到下单时间,生成订单的服务器IP,商品所属类型等信息,我们排查问题时,拿到订单号,就能事件发生的大概时间段,生成订单的时间和服务器IP,不至于大海捞针一样。大家可以观察下,我们很多的外卖单号,快递单号一眼就能看出来下单时间。有些外人一眼可能看不出来,开发者用工具把订单编号一解析就能得到这些信息。不是说UUID不好,就是说订单编号命名具有实际意义非常重要。
下面我们说下如何用ZK实现分布式系统命名服务。
ZK中每一个数据节点都能维护一份子节点的顺序序列,当客户端对其创建一个顺序子节点的时候,ZK会自动以后缀的形式在其子节点上添加一个序号,这个序号是唯一的。我们拿到这个序号再组合上自己的业务场景的标识,就能得到一个唯一的分布式ID。
4、分布式协调/通知
作为一个任务执行的协调者。书中讲了MySQL数据复制总线Mysql_Replicator的例子,下面我说下自己的理解。场景是:MySQL数据库实例之间进行异步数据复制和数据变化通知。
比如,有一个MySQL主库,有6个从库需要主库的数据。启动1个job去消费binlog中的数据,把数据同步给6个从库。
1、注册任务;
执行实例在启动时都到ZooKeeper下去注册同一个任务,如果任务已经存在,则无需注册,比如任务的名字叫:/mysql_ replicator/tasks/copy_hot_ item
这一步仅仅是为了在ZooKeeper下创建一个任务;
2、任务热备份
目的:为了应对复制任务故障或者复制任务所在主机故障,复制组件采用“热备份”的容灾方
式,即将同一个复制任务部署在不同的主机上,我们称这样的机器为“任务机器”,主、备任务机器通过ZooKeeper互相检测运行健康状况。
为了实现热备份,ZooKeeper要做两件事:1、选择一个主机执行任务;2、手机主、备机器的信息。所以有了后面的操作:无论第一步是否创建了任务节点,每台任务机器都需要在/mysql_ replicator/tasks/copy_hot_ item/instances节点上将自己的主机名注册上去,这里是一个临时的顺序节点。通常一个完整的节点名如下,最后的序列号 “1” ,就是临时顺序节点的精华所在:
/mysql_ replicator/tasks/copy_hot_ item/instances/[Hostname]-1
完成节点的创建后,每台任务机器都可以获取到自己创建的节点的完全节点名以及所有子节点的列表,然后通过对比,判断自己是否是子节点中序号最小的。如果自己是序号最小的子节点,那么将自己的运行状态设置为RUNNING,其余的任务机器则将自己设置为STANDBY——我们将这样的热备份策略成为“小序号优先”策略。
这时候,/mysql_ replicator/tasks/copy_hot_ item/instances/[192.168.0.100]-1 是RUNNING状态,/mysql_ replicator/tasks/copy_hot_ item/instances/[192.168.0.101]-2和/mysql_ replicator/tasks/copy_hot_ item/instances/[192.168.0.102]-3 是STANDBY状态。
3、热备切换
完成运行状态的标识后,任务的客户端机器就能够正常工作了,我们完成了任务热备份,其中标记为RUNNING的客户端机器为主,进行正常的数据复制,而标记为STANDBY的客户端机器则进入待命状态。所有机器的状态信息都注册到ZooKeeper上。
热备切换,是为了在RUNNING状态的机器宕机时,能迅速选出新的RUNNING节点来执行任务。要做到这一点,需要做两件事:1、RUNNING状态的机器宕机要通知到待命状态的机器;2、选新的节点继续进行数据复制。
第1件,利用ZooKeeper的通知机制。标记为STANDBY的机器都需要在/mysql_
replicator/asks/copy_ hot_ item/instances 节点上注册一个“子节点列表变更”的Watcher监听,用来订阅所有任务执行机器的变化情况一旦RUNNING机器宕机与ZooKeeper断开连接后,对应的节点就会消失,于是其他机器也就接收到了这个变更通知,从而开始新一轮的RUNNING选举。
第2件,小序号优先策略。标记为STANDBY的客户端机器都能拿到自己创建的节点的完全节点名以及所有子节点的列表,再次按照“小序号优先”策略,如果自己是序号最小的子节点,那么将自己的运行状态设置为RUNNING,其余的任务机器则将自己设置为STANDBY。
4、记录执行状态
使用了热备份,那么RUNNING任务机器就需要将运行时的上下文状态保留给STANDBY任务机器。在这个场景中,最主要的上下文状态就是数据复制过程中的一些进度信息,例如Binlog日志的消费位点,因此需要将这些信息保存到ZooKeeper上以便共享。在Mysql _Replicator 的设计中,选择了/mysql_replicator/tasks/copy_ hot_item/lastCommit作为Binlog日志消费位点的存储节点,RUNNING任务机器会定时向这个节点写入当前的Binlog日志消费位点。
5、控制台协调
在Mysql_Replicator 中,Server 主要的工作就是进行任务的控制,通过ZooKeeper来对不同的任务进行控制与协调。Server会将每个复制任务对应生产者的元数据,即库名、表名、用户名与密码等数据库信息以及消费者的相关信息以配置的形式写入任务节点/mysql_replicator/tasks/copy_ hot_ item 中去,以便该任务的所有任务机器都能够共享该复制任务的配置。
6、冷备切换
在该热备份方案中,针对一个任务,都会至少分配两台任务机器来进行热备份,但是在一定规模的大型互联网公司中,往往有许多MySQL实例需要进行数据复制,每个数据库实例都会对应一个复制任务,如果每个任务都进行双机热备份的话,那么显然需要消耗太多的机器。因此同时设计了一种冷备份的方案,它和热备份方案最大的不同点在于,对所有任务进行分组。执行实例进程运行后,去遍历该实例所属分组下所有task列表,遍历task下所有的instances节点,如果没有子节点则会创建一个临时的顺序节点,比如: /mysq_replicator/task_groups/groupl/copy_ hot_ item/instances/[Hostname]-1。 当然,在这个过程中,其他Core进程也会在这个instances节点下创建类似的子节点。和热备份中的“小序号优先”策略一样,顺序小的Core进程将自己标记为RUNNING,不同之处在于,其他Core 进程则会自动将自己创建的子节点删除,然后继续遍历下一个Task节点,我们将这样的过程称为“冷备份扫描”。就这样,所有Core进程在一个扫描周期内不断地对相应的Group下面的Task进行冷备份扫描。热备份是选出一个RUNNING进程,其他进程处于待命状态;冷备份选出一个RUNNING 进程,所有进程只需要在一个扫描周期内扫描task,在没有RUNNING进程时创建一个进程,不用待命,减少了机器资源的占用。
冷热备份对比
在热备份方案中,针对一个任务使用了两台机器进行热备份,借助ZooKeeper的Watcher通知机制和临时顺序节点的特性,能够非常实时地进行互相协调,但缺陷就是机器资源消耗比较大。而在冷备份方案中,采用了扫描机制,虽然降低了任务协调的实时性,但是节省了机器资源。大家可根据自己的场景选择。
5、Master选举
Master选举是一个在分布式系统中非常常见的应用场景。分布式最核心的特性就是能够将具有独立计算能力的系统单元部署在不同的机器上,构成一个完整的分布式系统。而与此同时,实际场景中往往也需要在这些分布在不同机器上的独立系统单元中选出一个所谓的“老大”,在计算机科学中,我们称之为Master。
在分布式系统中,Master往往用来协调集群中其他系统单元,具有对分布式系统状态变
更的决定权。例如,在一些读写分离的应用场景中,客户端的写请求往往是由Master来处理的;而在另一些场景中,Master则常常负责处理一些复杂的逻辑,并将处理结果同步给集群中其他系统单元。
利用ZooKeeper的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即ZooKeeper将会保证客户端无法重复创建一个已经存在的数据节点。也就是说,如果同时有多个客户端请求创建同一个节点,那么最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式环境中进行Master选举了。我们使所有客户端都去ZooKeeper下创建同一个临时的数据节点,比如叫/master_election/binding,创建成功的客户端为master。其他没有创建成功的客户端在/master_election建立一个子节点变更的watcher,用于监控master机器是否存活,一旦发现master挂了,立即开始新的选举。书中举了关于“海量数据处理与共享模型”的例子,感兴趣的同学可以了解一下。
6、分布式锁
因为分布式锁应用场景广泛且比较重要,所以单独写了一篇文章介绍,感兴趣的同学请移步Zookeeper-应用-分布式锁以及和Redis实现对比_小王师傅66的博客-CSDN博客
7、分布式队列
分布式队列,简单地讲分为两大类,一种是常规的先入先出队列,另一种则是要等到队列元素集聚之后才统一安排执行的Barrier模型。(下图来自搬运)
先入先出队列:
使用ZooKeeper实现FIFO队列,和共享锁的实现非常类似。FIFO队列就类似于一个全写的共享锁模型。所有客户端都会到/queue_ fifo这个节点下面创建一个临时顺序节点,如图所示:
执行流程如下图所示:
主要用的是ZooKeeper的注册通知机制和顺序节点功能。
Barrier:分布式屏障
Barrier原意是指障碍物、屏障,而在分布式系统中,特指系统之间的一个协调条件,规定了一个队列的元素必须都集聚后才能统一进行安排,否则一直等待。这往往出现在那些大规模分布式并行计算的应用场景上:最终的合并计算需要基于很多并行计算的子结果来进行。这些队列其实是在FIFO队列的基础上进行了增强,大致的设计思想如下:开始时,/queue_ barrier 节点是一个已经存在的默认节点,并且将其节点的数据内容赋值为一个数字n来代表Barrier 值,例如n=10表示只有当/queue_ barrier 节点下的子节点个数达到10后,才会打开Barrier。之后,所有的客户端都会到/queue_barrier 节点下创建一个临时节点,例如/queue_ barrier/192. 168.0.1。执行流程如下图:
主要是用的ZooKeeper的注册通知功能
总结
通过分析和学习ZooKeeper的数据发布/订阅,负载均衡,命名服务,分布式协调/通知等应用场景,使我更加深刻的理解了ZooKeeper,认识到了ZooKeeper在分布式场景中的强大,帮我更好的解决分布式系统中的问题。
借鉴:《从Paxos到Zookeeper(分布式一致性原理与实践)》—倪超