一、What
- 一个主从架构的分布式框架
- 给分布式框架提供协调服务(service)
作用
- 提供简版文件系统来存储数据
- 维护和监控存储的数据状态变化,通过监控数据状态变化达到基于数据的集群管理
- 主要用来解决分布式集群中应用系统的一致性问题
应用场景
1. 主备切换
2. 节点的上下线感知
3. 统一命名服务
4. 状态同步服务
5. 集群管理
6. 分布式应用配置管理
二、基本概念
ZooKeeper=简版文件系统(Znode)+原语+通知机制(Watcher)
- ZK文件系统
基于类似于文件系统的目录节点树方式的数据存储 - 原语
提供类linux指令进行操作 - Watcher(监听器)
数据节点ZNode
数据节点本质就是目录
持久节点 | 临时节点 | |
---|---|---|
非有序节点 | create | create -e |
有序节点 | create -s | create -s -e |
- 持久节点(无序)
节点创建以后,即便连接断开,除非主动删除,不然会一直存在 - 持久节点(有序)
创建节点的时候加上 -s ,会默认的在目录后加上数字
防止同一目录创建同名ZNode导致失败 - 临时节点(无序)
节点创建以后,一旦连接断开会自动删除
创建节点的时候通过 -t 指定 - 持久节点(有序)
同上
会话
与zk交互时会建立TCP长连接,称为会话
建立会话后,如果超过SessionTimeout时间,两者间没有通信,会话超时
特点:同一个会话里执行的指令是有序的;不同会话之间的指令是无序的
事务zxid
- 每个对数据的增删改操作都会生成一个对应的zxid
- zxid全局唯一,并且是自增的
- zxid通常是由64位数字,epoch+counter组成
Watch监视与通知
方式一(轮询):ZooKeeper以远程服务的方式,被客户端访问;客户端以轮询的方式获得znode数据,效率会比较低(代价比较大)
方式二(通知机制):客户端在znode上注册一个Watcher监视器,当znode上数据出现变化,watcher监测到此变化,通知客户端
- Watch作用流程
- 客户端在服务器端,注册的事件监听器
- watcher用于监听znode上的某些事件
- 比如znode数据修改、节点增删等
- 当监听到事件后,watcher会触发通知客户端
- 节点上下线原理
① 节点1(client1)创建临时节点
② 节点2(client2)在临时节点,注册监听器watcher
③ 当client1与zk集群断开连接,临时节点会被删除
④ watcher发送消息,通知client2,临时节点被删除的事件
用到的zk特性:Watcher+临时节点
好处:通过这种方式,检测和被检测系统不需要直接关联(如client1与client2),而是通过ZK上的某个节点进行关联,大大减少了系统耦合。
三、HDFS HA方案
工作原理
ZooKeeper使用原子广播协议叫做Zab(ZooKeeper Automic Broadcast)协议
- Zab协议有两种模式
- 恢复模式(选主):因为ZooKeeper是主从架构;当ZooKeeper集群没有主的角色leader时,从众多服务器中选举leader时,处于此模式
- 广播模式(同步):当集群有了leader后,客户端向ZooKeeper集群读写数据时,集群处于此模式
- 为了保证事务的顺序一致性,ZooKeeper采用了递增的事务id号(zxid)来标识事务,所有提议(proposal)都有zxid
监听器
注册:客户端向ZooKeeper集群注册监听器
监听事件:监听器负责监听特定的事件
回调函数:当监听器监听到事件的发生后,调用注册监听器时定义的回调函数
HA原理
HDFS HA方案,主要分两部分:
①元数据同步
- 在同一个HDFS集群,运行两个互为主备的NameNode节点。
- 一台为主Namenode节点,处于Active状态,一台为备NameNode节点,处于Standby状态。
- 只有Active NameNode对外提供读写服务,Standby NameNode会根据Active NameNode的状态变化,在必要时切换成Active状态。
-
JournalNode集群
- 在主备切换过程中,新的Active NameNode必须确保与原Active NamNode元数据同步完成,才能对外提供服务
- 所以用JournalNode集群作为共享存储系统;
- 当客户端对HDFS做操作,会在Active NameNode中edits.log文件中作日志记录,同时日志记录也会写入JournalNode集群;负责存储HDFS新产生的元数据
- 当有新数据写入JournalNode集群时,Standby NameNode能监听到此情况,将新数据同步过来
- Active NameNode(写入)和Standby NameNode(读取)实现元数据同步
- 另外,所有datanode会向两个主备namenode做block report
②主备切换
- 每个NameNode节点上各有一个ZKFC进程
- ZKFC即ZKFailoverController,作为独立进程存在,负责控制NameNode的主备切换
- ZKFC会监控NameNode的健康状况,当发现Active NameNode异常时,通过Zookeeper集群进行namenode主备选举,完成Active和Standby状态的切换
- ZKFC在启动时,同时会初始化HealthMonitor和ActiveStandbyElector服务
- ZKFC同时会向HealthMonitor和ActiveStandbyElector注册相应的回调方法(如上图的①回调、②回调)
- HealthMonitor定时调用NameNode的HAServiceProtocol RPC接口(monitorHealth和getServiceStatus),监控NameNode的健康状态,并向ZKFC反馈
- ActiveStandbyElector接收ZKFC的选举请求,通过Zookeeper自动完成namenode主备选举
- 选举完成后回调ZKFC的主备切换方法对NameNode进行Active和Standby状态的切换
主备切换过程
- ① 启动NameNode,ZKFC,此时两个NameNode的状态都是竞选状态
- ② 两个ZKFC分别通过ActiveStandbyElector发起NameNode的选举
通过zookeeper的写一致性以及临时节点来实现 - ③ 发起主备选举的时候,ActiveStandbyElector会尝试在zookeeper的某个目录下创建一个临时节点,zookeeper的写一致性会保证只有一个节点创建成功
- ④ 创建成功的ActiveStandbyElector通过回调方式通知ZKFC,将对应的NameNode切换为Active状态;创建失败的也通过同样方式将NameNode切换为Standby状态
- ⑤ 无论是否创建成功,这些ActiveStandbyElector都会监听那个目录;
当Active NameNode对应的HealthMonitor监控到NameNode异常时,会告知ZKFC,ZKFC通过ActiveStandbyElector删除所创建的临时节点,以及目录A
若是ZKFC是因为异常断开的连接,那么目录A还会存在 - ⑥ 此时处于Standby的NameNode会监控到这个消息
它首先会通过判断目录A是否存在来确认情况
如果是正常关闭的,则发起主备选举,成功创建临时节点,并且将NameNode的状态切换为Active
如果是异常关闭的,则会
1、通过RPC调用,试图让之前的Active的NameNode切换为StandBy
2、隔离:① 发送kill指令 ② 使用hadoop的隔离方式
脑裂
在分布式系统中双主现象又称为脑裂,由于Zookeeper的“假死”、长时间的垃圾回收或其它原因都可能导致双Active NameNode现象,此时两个NameNode都可以对外提供服务,无法保证数据一致性
- 隔离
对于生产环境,这种情况的出现是毁灭性的,必须通过自带的隔离(Fencing)机制预防此类情况 - 原理
ActiveStandbyElector成功创建ActiveStandbyElectorLock临时节点后,额外创建一个ActiveBreadCrumb持久节点
ActiveBreadCurmb持久节点保存Active NameNode的服务器信息
当Active NameNode正常状态下断开与Zookeeper Session,会一并删除临时节点ActiveStandbyElectorLock和ActiveBreadCurmb持久节点
-
如果是异常断开的,那么此时临时节点ActiveStandbyElectorLock不存在,但是ActiveBreadCurmb持久节点还存在;Standby节点会收到监听器发来的提醒将要从Standby切换成Active时,会先通过ActiveBreadCurmb里的服务器信息做隔离
1、通过RPC调用,试图让之前的Active的NameNode切换为StandBy
2、隔离(hadoop提供这两种隔离):① 发送kill指令 ② 使用hadoop的隔离方式只有成功地fencing之后,选主成功的ActiveStandbyElector才会回调ZKFC的becomeActive方法transitionToActive将对应的NameNode切换为Active,开始对外提供服务
四、Zookeeper架构
ZooKeeper服务器四种状态:
looking:服务器处于寻找Leader群首的状态
leading:服务器作为群首时的状态
following:服务器作为follower跟随者时的状态
observing:服务器作为观察者时的状态
1、安其内
全新leader选举(重启集群)
原则:集群超过半数的服务器启动后,才能选出leader
选举规则:
- 初始化:每个节点都投自己一票,然后向其他所有节点发送自己票的信息
- 交换投票信息:接收其他节点的选举信息,与自己的那票进行比较
投票信息vote信息结构为(serverId, zxid)
会先比较zxid,zxid大的那台服务器胜出(zxid越大意味存储了更多的数据)
若是zxid相同则serverId大的那台服务器会胜出
比较结束后,每台服务器会更新自己的投票信息,继续给其他服务器发送 - 决定投票结果:假设服务器B接收到超过半数的票选举自己,则自己从looking切换为leading,其他服务器从looking切换为following
- 当服务器C启动时,发现已有Leader,不再选举,直接从Looking改为Following
全新leader选举(leading挂掉或者出现网络分区)
攘其外
读操作
- 常见的读取操作,如ls /查看目录;get /zktest查询ZNode数据
- 读操作
- 客户端先与某个zk任意一台服务器建立Session
- 然后,直接从此ZK服务器读取数据,并返回客户端即可
- 关闭Session
写操作
- ① 与zk任意一台服务器建立Session建立连接
- ② follower将写请求转发给leader(不执行)
- ③ leader收到消息后,发起proposal提案
- ④ 每台服务器都会收到proposal后会记录这次操作并向leader返回同意,但不执行
- ⑤ 超过半数quorum同意,则leader提交commit提案,leader执行该操作
- ⑥ leader通知所有节点也commit提交该提案;所有节点在所在服务器执行该操作
- ⑦ client连接的那台follower响应Client
五、原理
quorum仲裁
- 什么是仲裁quorum?
- 发起proposal时,只要多数派同意,即可生效
- 为什么要仲裁?
- 多数据派不需要所有的服务器都响应,proposal就能生效
- 提高集群的响应速度
- quorum数如何选择?
- 集群节点数 / 2 + 1
为什么集群节点数强烈建议奇数个?
5节点的比6节点的集群
- 容灾能力一样,
- quorum小,响应快
- 偶数个如果被网络分区平分,则不能提供服务
网络分区和脑裂
- 网络分区:网络通信故障,集群被分成了2部分
- 脑裂:
- 原leader处于一个分区;
- 另外一个分区选举出新的leader
- 集群出现2个leader
ZAB算法
每个节点都有一个计时器(150毫秒~300毫秒之间的随机数),时间到后会发起选举的操作
非leader节点会定时的向leader节点发送心跳,leader收到心跳后会返回信息,非leader节点收到这个信息后会重置计时器
-
当发生网络分区并且leader处于少数的那边时,其他follower会进行选举,假设B服务器选举成功,那么它会和其他follower进行zxid的比较
① 假设服务器C的zkid比服务器B的少10个,那么服务器B会将这十个操作封包发给服务器C来执行
② 假设服务器D的zxid比服务器B多10个,那么服务器B会让它将这十个记录都删除
做完以上操作后,服务器B才会从follower切换成leader,并且将epoch里的值进行加1,zxid进行重置 当网络恢复了之后,旧leader比较路径下的epoch值的时候,发现已经有新的leader产生,会将自己切换成follower,并且进行数据的同步
状态同步
完成选举后,zk之间会进行状态同步操作
- leader构建NEWLEADER封包,包含leader最大zxid值,广播给其他follower
- follower收到后会跟自己最大zxid比较,若是比它小则进行同步操作
- leader给每个需要同步的follower创建LearnerHandler同步线程,负责进行同步操作
- leader主线程等待LearnerHandler线程处理结果
- 只有当大部分follower完成同步,该集群才对外提供服务,相应写请求
-
- LearnerHandler线程处理逻辑
- 接收follower封包FOLLOWERINFO,包含此follower最大zxid(代称f-max-zxid)
- f-max-zxid与leader最大zxid(代称l-max-zxid)比较
- 若相等,说明当前follower是最新的
-
- 另外,若在判断期间,有没有新提交的proposal
- 如果有,那么会发送DIFF封包将有差异的数据同步过去.同时将follower没有的数据逐个发送COMMIT封包给follower要求记录下来.
- 如果follower数据id更大,那么会发送TRUNC封包告知截除多余数据.
- 如果这一阶段内没有提交的提议值,直接发送SNAP封包将快照同步发送给follower.
- 以上消息完毕之后,发送UPTODATE封包告知follower当前数据就是最新的了
- 再次发送NEWLEADER封包宣称自己是leader,等待follower的响应.
分布式锁
- 所有需要获取锁的引用都在 /locker路径下创建一个有序临时节点
- 该路径下序号最小的应用获取该锁,其他应用分别监视比自己小一点的那个ZNode
- 当序号最小的那个应用操作完后,断开连接时,比它稍微大一点的那个应用会获取锁
分布式锁主要是应用了zk的一致性、临时节点、watch监视器这几个特性来保证的
WHO
- NameNode使用ZooKeeper实现高可用.
- Yarn ResourceManager使用ZooKeeper实现高可用.
- 利用ZooKeeper对HBase集群做高可用配置
- kafka使用ZooKeeper(仅限0.9以及之前版本)
- 保存消息消费信息比如offset.
- 用于检测崩溃
- 主题topic发现
- 保持主题的生产和消费状态
常用操作
# 使用ZooKeeper自带的脚本,连接ZooKeeper的服务器
zkCli.sh -server node01:2181,node02:2181,node03:2181
#创建节点,并指定数据(必须要指定数据)
create /kkb kkb