ZooKeeper应用场景
数据发布与订阅
即所谓的配置中心,就是发布者经数据发布到ZooKeeper的一个或一系列节点上,供订阅者进行数据订阅,进而达到动态的获取数据的目的,实现配置信息的集中式管理和数据的动态更新
如数据库切换的应用场景,看看是如上实现的
我们需要把数据管理的配置文件写入到该数据节点中,数据如下
代码语言:javascript复制jdbc.user=root
jdbc.password=root
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/spring
jdbc.driverClass=com.mysql.jdbc.Driver
当机器启动的时候,从zookeeper节点读取数据,一旦节点数据改变就会通知所有订阅该节点的客户端,而达到数据变更通知
负载均衡
负载均衡是一种常见的技术,分为硬件负载均衡和软件负载均衡,今天我们看的即使软件负载均衡,介绍一种动态DNS方案(Dynamic DNS)
每个应用都会在zookeeper创建一个属于自己的数据节点作为域名配置在根节点,例如/DDNS/app1,每个应用都会将自己的域名配置上去,
此时域名的解析有每个应用自行解决,会主动获取一份ip和端口的配置,同时在域名节点注册一个watcher监听,随时收到域名变更的通知,
当域名变更的时候,就会向订阅的客户端发送事件通知,不需要每个机器都手动更改而带来繁琐的工作。
命名服务
命名服务是一种常见的场景,分布式系统中,被命名的实体通常可以是集群的机器,提供服务地址,或远程对象等,如常见的RPC,RMI中的服务地址列表,通过使用命名服务,客户端应用能够根据指定名字获取资源的实体,服务地址和提供者的信息等,
比如dubbo中使用zookeeper作为命名服务,维护全局的服务地址列表
也可以使用zookeeper实现全局唯一id,
- 所有客户端想对应类型节点下create()创建一个顺序节点,例如job-
- 节点创建完之后,create接口返回完整的节点,例如job-0000001
- 客户端达到这个之后,在拼接type类型,例如type2-job-000001,这个就作为全局唯一ID了
分布式协调或通知
分布式协调和通知是分布式不可缺少的一个环节,通常用来协调整个系统的运行流程,例如分布式事物的处理,机器间的互相协调,引入这协调者,继而把分布协调者从应用中分离出来,从而大大减少系统之间的耦合性,提高系统的可扩展性。
zookeeper根据特有的watch注册与异步通知机制,能够很好的实现分布式环境下不用机器,甚至不同系统之间的协调与通知,从而实现对数据变更的实时处理。
例如Mysql数据复制总线,Mysql_Replcator
core,实现数据复制的核心逻辑,将数据复制封装成管道,并抽象出生产者和消费者,其中生产者就是mysql的binlog日志
server,控制启动和停止复制的任务
monitor,监控任务的转态,如果任务出现故障会进行告警
任务注册,
core进程启动的收,首先向zookeeoer中/mysql_replicator/tasks节点注册任务,如复制热门商品,所有的任务启动的收,都会在这个节点创建一个子节点,如/mysql_replicator/tasks/copy_hot_time(任务节点)。如下图
任务热备份
为了应对复制任务万一故障了,复制组件采用热备份的容灾方式,即将任务部署在不同的机器,这个机器成为任务机器,
为了实现热备份,无伦那个机器启动,都会向创建任务节点,如上图的。
代码语言:javascript复制/mysql_replicator/tasks/copy_hot_item/instances/[Hostname]-1
/mysql_replicator/tasks/copy_hot_item/instances/[Hostname]-2
这里注册的节点是临时顺序节点,完成了注册之后,比较节点序号小的为runnung,其他就为standby,这中热备份称为小序号优先策略
热备份切换
当完成任务状态标识之后,任务就能正常工作,但是当正在running的任务执行过程中出现了故障,就需要重新比较序号小的任务,标记为running状态,注意的其他状态为standby的都需要在/mysql_replicator/tasks/copy_hot_item/instances节点注册一个watcher监听,用来订阅任务执行机器的变化情况,一旦出现问题,对应的节点就会消失,此时就会新一轮的running选举
记录执行状态
这个是为了记录正在运行机器的上下文信息,方便给其他standby任务保留信息,例如binlog日志的消费位点,如上图的/mysql_replicator/tasks/copy_hot_item/lastCommit这个节点保存了binLog日志消费位点的存储节点,running任务机器会定时向这个节点写入当前的binlong日志消费位点
控制台协调
上面我们已经了解了mysql_replicator是如何进行分布式任务协调的,现在我要知道server是如何管理core组件的,这个server主要是进行任务的控制,通过zookeeper来对不同任务进行控制与协调,server将每个复制任务对应的元数据,即库名,表名,用户名,密码等信息存储在任务节点/mysql_replicator/tasks/copy_hot_item 中去,以便该任务的所有机器能够共享该复制任务的配置.
冷备切换
我基本了解了mysql_replicator的工作原理,但是我们进行热备份的时候,至少会有两台机器,如果当有多个mysql实例需要进行数据复制,每个数据库实例对应一个复制任务,就会消耗太多的机器
因此,提出了冷备份的方案,不同的是冷备份进行了分区如下图
冷备份基本和热备份思路一致,但是不同的是core进行在启动之后找到对应的group1下的所有task类表,如果没有子节点,则会创建一个子节点,如下图
代码语言:javascript复制/mysql_replicator/task-groups/group1/copy_hot_item/instances/[Hostname]-1
依然会按照序号小的标记为running,如果不是最小的,则core进程自动删除将自己的子节点删除,然后遍历下一个task节点,这个过程叫做冷备份扫描,所有core进程在一个扫描周期内不断的对相应的group下面的task进行冷备份扫描。
冷热备份对比
热备份借助zookeeper的watcher通知机制和临时顺序节点特性,能够非常实时的进行互相协助,但是就是对机器消耗比较大,而冷备份采用扫描策略,虽然减低了任务协调的实时性,但是节省了机器的资源,
热备份技术一个运行多个等待,冷备份在于一个运行,系统轮询判断是否有一个在运行,只要一个在运行就遍历下个任务,如果一个都没有运行这个任务,就让自己运行。
一种通用的分布式系统机器通信方式
心跳检测
基于zookeeper临时节点的特点,可以让不同的机器在zookeeper下创建历史节点,不同机器可以判断临时节点是否存在,判断机器是否存活
工作进度
同理是在zookeeper上创建临时节点,判断机器是否存活,也可以把自己的任务进度写到临时节点上,以便能够实时获取到任务的执行速度
系统调度
一份分布式系统由控制台和一些客户端系统组成,控制台职责就是需要以下执行发送给客户端,以控制他们进行相应的业务逻辑,后台可以在做一些操作,实际上就是修改某些节点的数据,然后把这个数据变更通知给订阅的客户端。
集群管理
随着分布式系统规模日益扩大,集群中的机器规模也随之变大,因此如何管理好集群也很重要
集群管理,分为两部分,集群的监控和集群控制两块,前者对集群运行时状态的收集,后者是集群的控制和操作,
由于zookeeper有以下两大特点
- 客户端可以对zookeeper的数据及诶单注册watcher监听,那么当这个节点内容发生变化的时候,就会通知向订阅的客户端发去通知
- 在zookeeper中进行注册的临时节点,一旦客户端和服务器之间的会话失效,这个临时节点就会消失
使用zookeeper管理分布式日志收集系统
一个典型的日志系统的架构设计中,整个日志系统会把所有的日志机器(日志源机器)分为多个分组,每个分组对应一个收集器,这个收集器就是用于收集日志,
此时我们需要解决两个问题
变化的日志源机器
生产环境,我们就成进行扩容,机房迁移,后者硬件问题,因此日志源机器通常也在不断变化
变化的日志收集机器
日志收集器也是一样,要进行变更或扩容,可能有新的机器加入,也有可能机器退出情况
上述两个问题都是可以用zookeeper解决
注册收集器机器
日志收集器进行运行注册到zookeeper,即在zookeeper上创建一个节点作为收集器的根节点,例如/logs/collector,每个收集器机器在启动的时候,都会在收集器节点下创建自己的节点,如下/logs/collector/[hostname]
任务分发
上一步我们已经把收集器都创建好自己的对应节点了,此时系统根据收集器节点的个数,将所有日志源机器分成对应的若干组,然后经分组后的机器分别写到收集器机器创建的子节点上去(/log/collector/host1),这样每个收集器机器能够从自己对应的收集器节点上获取日志源列表,进而开始日志收集工作
状态汇报
在处理完上面操作之后,我们还需要进行考虑的就是收集器可能随时挂掉,因此在收集器的子节点创建一个状态的子节点,即/log/collector/host1/status,这个收集器都需要定期向该节点写入自己的状态信息,我们把这个看做一个心跳检测机器,通常收集器会把自己的工作进度写入这个节点,
动态分配
如果当收集器进行扩展或扩容,zookeeper监控到节点变更,就要开始进行任务的重新分配,此时日志系统要进行之前分配给该收集器的任务进行转移,有两种办法
- 全局动态分配,如果有新的机器加入,就会把所有的日志源机器再一次的进行分组,然后将其分配给剩余的收集器机器
- 局部动态分配,就是小范围内进行任务的动态分配,这个策略,每个收集器在汇报自己日志收集器的同时,也会把自己的负载汇报上去,这种策略中,当收集器机器挂了,就会把之前分配给这个机器的任务重新分配到那些负载低的机器,当加入新的收集器机器,就会分配到负载高的机器
Master选举
分布式最核心的是能够将具有独立计算能力系统单元部署在不同的机器上,构成一个完整的分布式系统,但是分布式系统分布在不同的机器上,往往是要选出一个老大,这个老大即master,而这种Master就可以利用zookeeper实现
比如,我们一些分布式中的读写分离,客户端的写请求往往就是有Master处理,然后同步给其他集群的系统单元。
分布式环境中,我们经常遇到这种创景,集群中的所有系统单元需要对前端业务提供数据,比如一个商品id,而这种id是从海量数据处理中计算得到的,这种计算是一个非常消耗IO和CPU的,因此我们就可以使用zookeeper选出集群的一个系统单元,单独计算这个结果,然后共享给集群中其他客户端,这样大大减少了重复的劳动力,提高性能。
一个简单的广告投放系统后台场景为例,整个系统上可以分成客户端集群,分布式缓存系统,海量数据处理总线和zookeeper四部分
根据zookeeper强一致性,能够保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即zookeeper保证无法创建一个已经存在的节点,最终只有一定只有一个客户端请求能够创建成功,利用这个特性选出集群的master.
同时其他客户端可以在已经创建的节点注册watcher,用于当前master机器存活,一旦发现master宕机,其余客户端将重新进行master选举。
分布式锁
使用zookeeper如何实现分布式锁,分为排他锁和共享锁
排他锁
如果事物T对数据对象O加上了排他锁,整个加锁过程,只允许事物T对数据O进行读写和更新操作,其他任何事物都不能对这个数据进行任何类型的操作,直到事务释放排他锁
获取锁
这个是zookeeper强一致性,例如当客户端视图想通过create接口,在/exclusive_lock节点创建子节点/excluesive/lock。最终只要一个客户端创建成功,其他客户端注册监听这个节点的watcher,以便监控lock节点的变更情况
释放锁
由于我们建立的临时节点,如下情况就是删除节点
- 获取锁的客户端宕机,则删除节点
- 正常逻辑执行完成,客户端主动删除临时节点
整个流程如下
共享锁
又称读锁,如果事物T对数据独享O加上了共享锁,那么当前事务只能对数据O进行读取操作,其他事物也只能对着数据加共享锁,一直到该数据对象上的所有共享锁释放
获取锁
根据共享锁的定义,不同事务都可以同时对同一个数据对象进行读操作,而更新操作必须当前没有任何事物进行读写操作的情况下进行,基于这原则,我们看看zookeeper是如何确定分布式读写顺序
- 创建完节点后,获取/shared_lock节点下的所有子节点,并对该节点注册子节点watcher监听
- 确定自己的节点序号在所有子节点的顺序
- 对于读请求
- 如果没有比自己序号小的子节点,或比自己小的节点都是读请求,就可以获取到锁,同时开始执行逻辑
- 如果有比自己小的节点是写请求,就必须等待
- 对于写请求
- 如果自己不是序号最小的节点,那么就需要等待
- 接受到watcher通知后,重复步骤1
释放锁和排他锁是一样逻辑
整体共享锁的流程
? 群效应
我们知道当前的共享锁被删除之后,会通知其他机器进行获取锁的操作,但是我们知道实际上这个通知只对第二小的节点有效果,其他通知都是徒劳的,大量的通知,和子节点列表两个操作重复运行,如果集群规模比较大,不仅对zookeeper服务器造成比价比较大的影响,如果同一时间多个节点对应的客户端完成事务,或者中断事务,zookeeper短时间呢就会向其余客户端发送大量的时间通知,这就是所谓的羊群效应
改进后的分布式锁实现
这里仅仅在每个锁竞争者,只需要关注比自己序号小的那个相关节点的变更情况是否存在即可,
分布式队列
FIFO,先进先出
使用zookeeper实现FIFO队列,和共享锁类似,所有客户端都会到/queue_fifo.这个节点下面创建临时节点,具体逻辑如下
分布式屏障
开始时候在创建/queue_barrier默认节点,然后将将其节点的数据内容复制为一个数据n代表barrier值,如果n=10,表示/queue_barrier的子节点到达10个之后,才会打开barrier.具体流程如下