ZooKeeper是一个高可用的分布式数据管理与协调框架
- ZAB算法的实现,很好的保证了分布式系统数据一致性
数据发布/订阅
- 数据发布/订阅系统,即所谓配置中心
- 发布者发布数据到ZooKeeper的一系列节点上,供订阅者订阅,达到动态更新数据的目的
- 实现数据的集中式管理和动态更新
发布/订阅系统一般有推(Push)和拉(Pull)两种模式
- 推(Push):服务端主动将数据推送给客户端
- 拉(Pull):客户端主动去服务端拉取最新数据,一般客户端采取定时轮询的策略
- ZooKeeper才去的是推拉结合的策略
- 客户端向服务端注册需要关注的节点,服务端数据发生变化的时候向客户端推送watcher事件通知,客户端再主动去服务端拉取数据
负载均衡
- 常见的计算机网络技术,对多台计算机、CPU、磁盘驱动器等分配负载;
- 达到优化资源使用、最小化响应时间、最大化吞吐率、避免过载的目的
- 分为软负载和硬负载;ZooKeeper属于软件负载
比较典型的是DNS 服务:
- DNS是(Domain Name System)域名系统的缩写
- 可以看做是一个超大规模的分布式映射表(域名-->IP),方便人们通过域名访问互联网站点
- 实际开发中通常采用本地host 绑定来实现域名解析
基于ZooKeeper实现的动态域名解析方案(DDNS :Dynamic DNS):
- 域名解析由每个应用自己解决;
- 每个应用还会在数据节点上注册一个数据变更Watcher,以便收到变更通知;
- 好处:
- 一方面避免带来域名无限增长带来的集中式管理压力
- 另一方面域名变更,逐台更新本地HOST的繁琐
- 好处:
自动化的DNS服务器
- 整个框架是域名解析的服务提供者,需要域名解析的客户属于消费者
系统运行过程:
- 域名注册
- 每个服务启动过程中,把自己的域名信息注册到Register集群
- Register将信息写入ZooKeeper对应域名数据节点
- 域名解析:
- 消费者向Dispatcher 发起查询,Dispatcher返回结果
- 域名探测:
- DDNS系统对所有注册的域名进行可用性检测(健康度检测)
- 分两种:
- 服务端主动发起健康度心跳检测,一般需要服务端和客户端建立TCP长连接
- 客户端发起健康度心跳检测
- DDNS 采用的是每个服务提供者定时向Scanner 汇报健康度的形式(第二种)
- Scanner会记录时间,一旦超过5秒没收到状态汇报,就会清理域名
命名服务(Name Service)
- 命名服务是分布式系统最基本的公共服务之一
- 分布式系统中,被命名实体:集群中的机器,提供的服务地址或远程对象等,都可称为名字(Name)
- 比较常见的如:分布式服务框架(RPC、RMI),客户端程序通过名字可以获取资源实体、服务地址、提供者信息等
- ZooKeeper命名服务,通过资源引用的方式对资源进行定位和使用
ZooKeeper实现分布式全局唯一ID分配机制
- 在单库单表数据库中,auto_increment 就好
- 分布式环境下全局唯一ID:
- UUID:universial unique identity,通用唯一识别码
- GUID:Global unique identity,是hibernate 对UUID 的实现
- UUID 能非常简便的实现唯一性,存在缺陷:
- 包含32位字符和4个短线字符串,长度过长
- 含义不明
- ZooKeeper全局唯一id生成:
- 客户端根据任务类型,在各自任务类型下顺序生成id
- 节点名拼接上类型,获取完整全局唯一id
分布式协调/通知
- 分布式协调/通知服务,是分布式系统必不可缺环节,不同分布式组件有机结合起来关键所在
- 对于多台机器上部署运行的应用,需要一个协调者(coordinator)
- 从应用中抽离出来分布式协调
- 解耦各个系统之间耦合性,增强扩展性
- ZooKeeper中特有的Watcher机制和异步通知机制
- 能很好地实现数据变更的实时处理
mysql数据复制总线(mysql_replicator)
- 实时数据复制框架,在不同mysql数据库实例之间进行实时异步数据复制和数据变化通知
- 整个系统包括:mysql数据库集群、消息队列系统、任务管理监控平台、ZooKeeper集群等组件
- 包含数据生产者、复制管道、数据消费者等部分
- 分三个核心子模块,每个模块都是单独进程
- 每个模块独立进程部署在服务端
- 运行时数据和配置信息均保存在ZooKeeper
- web控制台根据ZooKeeper上的信息获取到后台数据,发布控制信息
任务注册:
- core启动的时候首先向数据节点(/mysql_replicator/taskes)注册”任务列表节点“
- 比如一个复制热门商品的任务
任务热备份
- 为了应对复制中可能出现的问题(如:复制任务故障或复制任务主机故障)
- 复制组件采用热备份的容灾形式,即将同一个复制任务,部署在不同的主机上
- 主、备任务主机通过ZooKeeper相互检查健康状况
- 下图中,HostName1、2这样的临时顺序节点
- 完成子节点创建后,每台机器都能获取自己的节点;
- 对比判断自己是否是最小节点,最小状态RUNNING、其他STANDBY(小序号优先策略)
热备切换
- RUNNING正常复制、STANDBY待命
- RUNNING故障,STANDBY最小节点变成RUNNING
- 具体实现:所有STANDBY注册子节点数据变更Watcher 到RUNNING机器
- RUNNING机器与ZooKeeper断开连接,对应数据节点就会消失,Watcher会受到事件
记录执行状态
- RUNNING机器将运行时的任务上下文状态保留给STANDBY机器
- 写入lastCommit节点
控制台协调
- Server管理core组件,将其复制相关元数据,写入数据节点,以备其他机器共享
冷备切换
- core进程被分配对应Group,扫描组下task列表,如下图:
冷热备份对比
- 热备份:每个任务分配两台机器,Watcher机制交互,实时性强,资源浪费大
- 冷备份:扫描的方式,降低了实时性,节省资源
一种分布式系统机器间通信方式:
- 机器间通信无外乎:心跳检测、工作进度汇报、系统调度三种类型
- 心跳检测
- 通常机器间相互建立连接,确定心跳(对方是否依然存活)
- 使用ZooKeeper实现,各个机器都在ZooKeeper指定位置创建临时节点,根据临时节点判断对应客户端是否存活
- 工作进度汇报
- 任务分发系统,会把任务分发给不同的机器,各个机器向其汇报任务进度
- 每个任务客户端都在这个节点下面创建临时子节点,这样不仅可以判断机器是否存活,
- 同时各个机器可以将自己的任务执行进度写到该临时节点中去,以便中心系统能够实时获取任务的执行进度
- 系统调度
- Zookeeper能够实现如下系统调度模式:分布式系统由控制台和一些客户端系统两部分构成
- 控制台的职责就是需要将一些指令信息发送给所有的客户端,以控制他们进行相应的业务逻辑
- 后台管理人员在控制台上做一些操作,实际上就是修改Zookeeper上某些节点的数据
- Zookeeper可以把数据变更以事件通知的形式发送给订阅客户端
集群管理
- 集群监控和集群控制
- Zookeeper的两大特性:
- 客户端如果对Zookeeper的数据节点注册Watcher监听,那么当该数据节点内容或是其子节点列表发生变更时,Zookeeper服务器就会向订阅的客户端发送变更通知。
- 对在Zookeeper上创建的临时节点,一旦客户端与服务器之间的会话失效,那么临时节点也会被自动删除。
分布式日志收集系统
- 重点看收集器模块
- 日志源机器分为多个组,每个组有一个收集器(收集机器)
通常需要解决两个问题:
- 变化的日志源机器
- 变化的收集器机器
1、注册收集器机器
- 每个收集器机器启动时都会在收集器节点下创建自己的节点,如/logs/collector/[Hostname]
2、任务分发
- 系统根据收集器节点下子节点的个数,将所有日志源机器分成对应的若干组
- 将分组后的日志源机器列表分别写到这些收集器机器创建的子节点
- 收集器机器就能够根据自己对应的收集器节点上获取日志源机器列表,进而开始进行日志收集工作。
3、状态汇报
- 完成任务分发后,机器随时会宕机
- 每个收集器机器上创建完节点后,还需要再对应子节点上创建一个状态子节点,如/logs/collector/host/status
- 每个收集器机器都需要定期向该结点写入自己的状态信息,这可看做是心跳检测机制,通常收集器机器都会写入日志收集进度信息
- 志系统通过判断状态子节点最后的更新时间来确定收集器机器是否存活。
4、动态分配
- 若收集器机器宕机,则需要动态进行收集任务的分配
- 收集系统运行过程中关注/logs/collector节点下所有子节点的变更,一旦有机器停止汇报或有新机器加入,就开始进行任务的重新分配
- 通常由两种做法:
- 全局动态分配:对所有的日志源机器重新进行一次分组,然后将其分配给剩下的收集器机器。
- 局部动态分配:
- 每个收集器机器在汇报自己日志收集状态的同时,也会把自己的负载汇报上去
- 如果一个机器宕机了,那么日志系统就会把之前分配给这个机器的任务重新分配到那些负载较低的机器,
- 同样,如果有新机器加入,会从那些负载高的机器上转移一部分任务给新机器。
上述步骤已经完整的说明了整个日志收集系统的工作流程,其中有两点注意事项:
- 节点类型
- 在/logs/collector节点下创建临时节点可以很好的判断机器是否存活,但是,若机器挂了,其节点会被删除,记录在节点上的日志源机器列表也被清除,
- 所以需要选择持久节点来标识每一台机器,同时在节点下分别创建/logs/collector/[Hostname]/status节点来表征每一个收集器机器的状态,
- 这样,既能实现对所有机器的监控,同时机器挂掉后,依然能够将分配任务还原。
- 日志系统节点监听
- 若采用Watcher机制,收集器节点状态变更可能很频繁,那么通知的消息量的网络开销非常大,
- 需要采用日志系统主动轮询收集器节点的策略,这样可以节省网络流量,但是存在一定的延时(考虑分布式日志收集系统定位,这点延时可接受)。
在线云主机管理
- 通常该类系统需求点如下:
- 解决:
- 采用ZooKeeper,将指定的Agent 部署到被监控机器上去,机器启动,会在ZooKeeper添加临时节点(这样断开时,临时节点也会被删除)
- Agent定时写入自己状态
Master选举
- 在分布式系统中,Master往往用来协调集群中其他系统单元,具有对分布式系统状态变更的决定权,
- 如在读写分离的应用场景中,客户端的写请求往往是由Master来处理,
- 或者其常常处理一些复杂的逻辑并将处理结果同步给其他系统单元。
- 利用Zookeeper的强一致性,
- 能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,
- 即Zookeeper将会保证客户端无法重复创建一个已经存在的数据节点。
- 具体过程:
- 首先创建/master_election/2013-09-20节点,客户端集群每天会定时往该节点下创建临时节点,如/master_election/2013-09-20/binding,
- 这个过程中,只有一个客户端能够成功创建,此时其变成master,
- 其他节点都会在节点/master_election/22013-09-20上注册一个子节点变更的Watcher,用于监控当前的Master机器是否存活,
- 一旦发现当前Master挂了,其余客户端将会重新进行Master选举。
分布式锁
- 分布式锁用于控制分布式系统之间同步访问共享资源的一种方式,
- 可以保证不同系统访问一个或一组资源时的一致性,主要分为排他锁和共享锁。
- 排他锁又称为写锁或独占锁
- 若事务T1对数据对象O1加上了排它锁,那么在整个加锁期间,
- 只允许事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作,直到T1释放了排它锁
- 获取锁,在需要获取排它锁时,所有客户端通过调用接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。
- Zookeeper可以保证只有一个客户端能够创建成功,没有成功的客户端需要注册/exclusive_lock节点监听。
- 释放锁,当获取锁的客户端宕机或者正常完成业务逻辑都会导致临时节点的删除,
- 此时,所有在/exclusive_lock节点上注册监听的客户端都会收到通知,可以重新发起分布式锁获取。
- 获取锁,在需要获取排它锁时,所有客户端通过调用接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/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。
-
-
-
释放锁,其释放锁的流程与独占锁一致。
羊群效应(惊群效应)
- 可以看到,host1客户端在移除自己的共享锁后,Zookeeper发送了子节点更变Watcher通知给所有机器,
- 然而除了给host2产生影响外,对其他机器没有任何作用。
- 大量的Watcher通知和子节点列表获取两个操作会重复运行,这样会造成系能鞥影响和网络开销,
- 更为严重的是,如果同一时间有多个节点对应的客户端完成事务或事务中断引起节点小时,Zookeeper服务器就会在短时间内向其他所有客户端发送大量的事件通知,这就是所谓的羊群效应。
可以有如下改动来避免羊群效应:
- 客户端调用create接口常见类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点。
- 客户端调用getChildren接口获取所有已经创建的子节点列表(不注册任何Watcher)。
- 如果无法获取共享锁,就调用exist接口来对比自己小的节点注册Watcher。
- 对于读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。
- 对于写请求:向比自己序号小的最后一个节点注册Watcher监听。
- 等待Watcher通知,继续进入步骤2。
此方案改动主要在于:每个锁竞争者,只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可。
注意:
- 改进方案主要是缩小锁作用范围,和多线程并发编程思路一样
- 改进后方案比较麻烦
- 并不是说非要用改进后的方案:
- 如果集群规模不大,资源相对没那么紧张,第一种方案简单直接
- 如果规模达到一定程度,精细管理锁的作用范围显得很重要
分布式队列
分布式队列可以简单分为先入先出队列模型和等待队列元素聚集后统一安排处理执行的Barrier模型。
- FIFO先入先出,先进入队列的请求操作先完成后,才会开始处理后面的请求。
- FIFO队列就类似于全写的共享模型,
- 所有客户端都会到/queue_fifo这个节点下创建一个临时节点,如/queue_fifo/host1-00000001。
-
创建完节点后,按照如下步骤执行:
1. 通过调用getChildren接口来获取/queue_fifo节点的所有子节点,即获取队列中所有的元素。
2. 确定自己的节点序号在所有子节点中的顺序。
3. 如果自己的序号不是最小,那么需要等待,同时向比自己序号小的最后一个节点注册Watcher监听。
4. 接收到Watcher通知后,重复步骤1。
Barrier分布式屏障
- 创建完节点后,按照如下步骤执行。
1. 通过调用getData接口获取/queue_barrier节点的数据内容,如10。
2. 通过调用getChildren接口获取/queue_barrier节点下的所有子节点,同时注册对子节点变更的Watcher监听。
3. 统计子节点的个数。
4. 如果子节点个数还不足10个,那么需要等待。
5. 接受到Wacher通知后,重复步骤3。
来源:oschina
链接:https://my.oschina.net/u/3847203/blog/2876714