Zookeeper是一个典型的发布订阅模式的分布式数据管理和协调框架,可以用来进行分布式数据的发布与订阅,而且通过其中丰富的数据节点类型与watcher事件通知机制,可以构建一系列分布式应用中设计的核心功能,下边对这些场景做下介绍。
数据发布订阅系统,我们通过发布者将数据信息存储到ZooKeeper的节点上,订阅者对数据进行订阅,达到动态获取数据的一个效果。
发布订阅系统一般由两种模式:推模式和拉模式,推模式服务端主动将数据更新发送给所有订阅的客户端;拉模式是客户端主动发起请求获取最新数据,客户端一般进行定时拉取。
Zookeeper采用的是推拉结合的方式:客户端向服务端注册自己关注的节点,如果阶段数据有变更,服务端会向客户端发送watcher时间通知,客户端收到后主动向服务器获取最新的数据。
应用场景举例:数据库切换配置管理
1.首先我们将需要管理的数据信息写入道指定的数据节点中。
2.集群中每台机器在启动初始化阶段,从指定的节点上读取配置信息,同时在改节点上注册一个数据变更的watcher监听。
3.在系统运行的过程中,可能会出现数据库切换的情况,这个时候需要进行配置变更。我们对节点上的内容进行更新,zookeeper会将数据变更的通知发给所有客户端,客户端在接受到这个变更通知后,重新对数据进行获取。
命名服务是我们经常可以见到的场景。在分布式系统中,被命名的实体通常可以是集群中的机器,提供的服务地址或者远程对象等。广义上命名服务的资源地址不一定都是实体资源,在分布式环境中,上层应用仅仅需要一个全局唯一的名字,类似于数据库中的唯一主键。
全局唯一ID
一般说起全局唯一ID,都会想到UUID,但是UUID本身存在一定的缺陷。
1.长度过长
UUID最大的问题在于生成的字符串过长,需要更多的一个空间区存储。
2.含义不明
UUID是一个包含32位字符和4个短线的字符串,类似于“e70f1357-f260-46ff-a32d-53a086c57ade”的一个字符串,无法从字母上看出任何含义。
调用Zookeeper节点创建的API接口可以创建一个顺序节点,并且在API返回值中会返回这个节点的完整名字。利用这个特性,我们可以来生成唯一id。
![在这里插入图片描述](https://img-blog.csdnimg.cn/d89d955630eb44518444f8c85493a553.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWluYW1vdG8gQ2hpc2Vp,size_17,color_FFFFFF,t_70,g_se,x_16
1.所有的客户端去在指定类型的任务下面创建一个顺序节点。
2.节点创建完毕后,会返回一个完整的节点名,例如“job-0000000003”.
3.客户端在拿到这个返回值后,拼接上type类型,就可以作为一个全局唯一的ID了。
集群管理包括集群监控和集群控制两大块,前者侧重对集群运行时状态收集,后者则是对集群进行控制与操作。
我们经常会有类似于如下的需求:
快速的统计处当前生产环境下一共有多少台机器。
快速获取到机器的上下线情况。
实时监控集群中每台主机的运行时状态。
在传统基于Agent的分布式集群管理体系中,会在每台机器上部署一个Agent,由这个Agent负责主动向指定的一个监控中心系统汇报自己所在机器的状态。改解决方案在集群规模变大后,会有一些弊端。
大规模升级困难
因为存在与每一天机器上,遇上大规模升级的情况会比较麻烦。
统一的Agent无法满足多样的需求
如果需要深入应用内部,对一些业务状态进行监控,如监控每个消费者对消息的消费状态或者在一个分布式任务调度系统中,需要对每个机器上任务的执行情况进行监控,这些业务耦合紧密的监控需求不适合由一个统一的Agent来提供。
编程语言多样性
随着越来越多编程语言的出现,各种异构系统层出不穷。如果使用传统的Agent方式,需要提供各种语言的Agent客户端。
zookeeper的两大特性
1.客户端如果对指定节点进行类监听,那么该数据节点的内容或者是其子节点列表发生变更时,Zookeeper会向订阅的客户端发送变更通知。
2.在Zookeeper上创建的临时节点,在客户端与服务器之间的会话失效之后,临时节点也会被自动删除。
利用这两大特性,可以实现集群机器存活监控系统,下面通过分布式日志系统这个典型应用来学习Zookeeper如何实现集群管理
对于大规模的分布式日志收集系统通常需要解决两个问题:
变化的日志源机器:生成环境中,每个应用的机器几乎每天都是变化的。
变化的收集器机器:日志收集系统自身也会有机器的变更或扩容,会出现新的收集器机器加入或是老的收集器机器退出的情况。
其实主要问题是如何快速,合理且动态地为每个收集器分配对应的日志源机器,使用zookeeper的步骤如下:
1.注册收集器机器
创建一个节点作为收集器的根结点,每个收集器在启动的时候都会在收集器下创建自己的节点。
2.任务分发
待所有收集器机器都创建好自己对应的节点后,系统根据收集器节点下子节点的个数,将所有日志源机器分成对应若干组,然后将分组后的机器列表写到这些收集器创建的字节点。
3.状态汇报
每个收集器机器在创建完自己的专属节点后,还需要在对应的字节点上创建一个状态子节点,每个收集器机器都需要定期向该节点写入自己的状态信息。
4.动态分配
如果收集器机器挂掉或是扩容了,就需要动态地进行收集任务的分配,一旦检测到由收集器机器停止汇报或是有新的收集器机器加入,就需要开始进行任务的重新分配,通常有两种做法:
全局动态分配:
对多有的日志源机器重新进行一次分组,然后将其分配给剩下的收集器机器。
局部动态分配:
每个收集器在汇报自己日志收集状态的同时,也会把自己的负载回报上去。在这种策略中,如果一个收集器挂了,那么日志系统会把之前分配给这个机器的任务重新分配到那些负载较低的机器上,如果有新的收集器机器加入,会从那些负载高的机器上转移部分任务给这个新加入的机器。
Master选举在分布式系统中是一个常见的应用场景。在分布式系统中,Master往往用来协调集群中其他系统单元,在集群的所有机器中选出一台机器作为Master,通常我们可以选择常见的关系型数据库中的主键特性来实现:集群中所有机器都向数据库中插入一条相同主键ID的记录,数据库会帮助我们自动进行主键冲突检查,所有进行插入操作的客户端机器中,只有一台机器能够成功,则该机器客户端成为Master。
借助数据库的这种方案确实可行,但如果当前选举出的Master挂了,没法通知我们这个事件。利用zookeeper则可以做到这一点,利用zookee的强一致性,能够很好保证在分布式高并发情况下节点的创建一定能保证全局唯一性,zookee会保证客户端无法重复创建一个已存在的数据节点,如果同时有多个客户端请求创建同一个节点,最终一定只有一个客户端能创建成功。
客户端集群每天都会定时往zookeeper上创建一个临时接到,只有一个客户端能够成功创建,这个客户端所在的机器就成为了Master。其他创建失败的客户端会在该节点上注册一个子节点变更的Watcher,用于监控当前master是否存活,一旦发现当前的Master挂了,其余客户端重新进行Master选举。
如果仅仅只是想实现Master选举的话,只需要一个能够保证数据唯一性的组件即可,如果希望能够快速地进行集群Mastee动态选举,可以基于Zookeeper来实现。
分布式所是控制分布式系统同步访问共享资源的一种方式。
使用Zookeeper实现分布式锁:
排他锁:排他锁是一种基本的锁类型。如果事务T1对数据对象O1加上了排他锁,那么在整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他食物只能等待T1释放排他锁。
定义锁:通过Zookeeper上的数据节点来表示一个锁
获取锁:在需要获取排他锁时,所有客户端试图去/exclusive_lock节点下创建临时子节点,创建成功的客户端被认为获取了锁,没有获取到锁的客户度在/exclusive_lock节点上注册一个子节点变更的Watcher监听。以便实时监听到lock节点的变更情况。
释放锁:在创建节点的时候会创建一个临时节点,因此在获取锁的客户端机器发生宕机时,改临时节点就会被移除。这场执行完业务逻辑后,客户端会主动将自己创建的临时节点删除。无论何种情况下移除了lock节点,Zookeeper都会通知所有注册了子节点变更watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取。
共享锁
共享锁又称为读锁,同样是一种基本的锁类型。如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对其进行读取操作,其他事务也只能对这个数据对象加共享锁。
定义锁:通过zookeeper上的数据节点来表示一个锁。
获取锁:所有客户端都会到/shared_lock这个节点下创建一个临时顺序节点,如果是读请求,就创建例如/shared_lock/host1-R-00000000001的节点;如果是写请求,那么就创建例如/shared_lock/host-W-0000000002的节点。
判断读写顺序:
1.创建完节点后,获取/shared_lock节点下所有子节点,并对该节点变更注册监听。
2.确定自己的节点序号在所有子节点中的顺序。
3.对于读请求,若没有比自己序号小的字节点或所有比自己序号小的子节点都是读请求,那么表明自己已成功获取到共享锁,同时开始执行读取逻辑,若有写请求,则需要等待。对于写请求,若自己不是序号最小的子节点,则需要等待。
4.接受到Watcher通知后,重复1
释放锁:与独占锁流程一致。
分布式队列可以简单分为两大类:一种是常规的FIFO先入先出队列模型,还有一种是等待队列元素聚集后统一安排处理执行的Barrier模型。
FIFO:FIFO队列是一种非常典型且应用广泛的按序执行的队列模型:先进入队列的请求操作先完成后,才会开始处理后面的请求。
使用Zookeeper实现FIFO队列,和共享锁的实现非常类似。
所有客户端到/queue_fifo这个节点下创建一个临时节点。
通过调用getChildren接口来获取所有子节点,判断自己节点序号在所有子节点中的顺序。
如果自己的序号不是最小,那么需要等待,同时比自己序号小的最后一个节点注册Watcher监听。
接受到Watcher通知后,重复步骤1.
Barrier:分布式屏障
Barrier原意是指屏障,而在分布式系统中,特指系统之间的一个协调条件,规定了一个队列的元素必须都聚集后才能统一进行安排,否则一致等待。这往往出现在那些大规模分布式并行计算的应用场景上:最终的合并计算需要基于很多并行运算的字结果来进行。
大致设计思路:开始时间:/queue_barrier节点是一个已经存在的默认节点,并且将其节点的数据内容赋值为一个数组n来代表Barrier值,例如n=10表示只有当/queue_barrier节点下的子节点个数达到10后,才会打开Barrier。之后,所有客户端都会到/queue_barrier节点下创建一个临时节点
创建完节点后按照如下步骤进行;
1.通过调用getData接口获取/queue_barrier节点的数据内容:10.
2.通过调研getChildren接口获取/queue_barrier节点下的所有字节的,同时注册对子节点变更的Watcher监听。
3.统计子节点的个数。如果子节点个数还不足10个,那么需要等待。
4.接受到Watcher通知后,重复步骤2。