zookeeper笔记
# 一、什么是zookeeper
zookeeper是一个分布式、开源的、用于分布式应用程序的协调服务,用来解决分布式集群中应用系统的一致性问题的分布式的服务框架。他是主从架构、能够基于类似于文件系统的目录节点树方式的数据存储。用来维护和监控存储数据的状态变化,从而达到基于数据的集群管理。
二、zookeeper的基本概念
zookeeper主要由3部分实现
zk文件系统,类似于文件系统目录树方式的数据存储
原语,zookeeper的基本命令
watcher(监听器)
数据节点znode
znode分为4类
-- 持久化节点类似文件夹
-- 临时节点。生命周期跟客户端会话session绑定,一旦会话失效,临时节点被删除
-- 有序节点。防止多个客户端在同一目录创建重名znode。改节点在创建的时候会自动在节点后面追加一个整型数字,由父节点维护的自增数字
三、会话
会话是客户端要对集群zookeeper集群进行读写操作得先和zookeeper服务器建立tcp长连接,此tcp长连接称为建立一个会话session。每个会话会有超时时间。客户端打开一个session中的请求会以fifo的顺序执行。
事务zxid
每个食物会有一个全局唯一的事务id用zxid表示,全局自增。通常是一个64位数据,表示第几位leader第几个事务
watcher监视与通知
客户端在znode注册一个watcher监视器,当znode上数据发生变化,watcher监测到此变化,通知客户端。
原子广播zab
zab协议有两种模式:
恢复模式(选主):因为zookeeper也是主从架构,当zookeeper集群没有主的角色leader时,从众多服务器中选举leader时,处于此模式。
广播模式(同步):当集群有了leader后,客户端向zookeeper集群读写数据时,集群处于此模式。
为了保证事务的顺序一致性,ZooKeeper采用了递增的事务id号(zxid)来标识事务,所有提议(proposal)都有zxid
hdfs ha方案
hdfs提出高可用方案,主要用于解决单点故障 zookeeper监听器有三个逻辑
- 注册:客户端向zookeeper集群注册监听器
- 监听事件:监听器负责监听特定事件
- 回调函数:当监听器监听到事件的发生后,调用注册监听器时定义的回调函数
关键逻辑:监听器和共享存储 JournalNode
主要分两个部分:元数据同步
-
元数据同步
-
在一个hdfs集群中,运行两个互为主备的namenode节点,主节点active namenode对外提供读写服务,备节点standby会根据active namenode的状态变化,在必要时候切换成active
-
journalNode集群:在主备切换的时候必须要确保元数据同步完成,才能够对外服务。所以用journal node集群作为共享存储系统。当客户端对hdfs做操作会在active namenode中edits.log文件中作日志记录,同时日志记录也会写入
journal集群,当有新数据写入journalnode集群时候,standy namenode能监听此情况将新数据同步过来。主备实现元数据同步,此外datanode会向两个主备namenode做block report。
-
-
主备切换过程
-
zkfc涉及角色
- zkfc:即ZKFailoverController,每个节点都会有一个zkfc进程,作为独立进程,负责控制namenode的主备切换。在启动时候会初始化HealthMonitor和ActiveStandbyElector服务
- HealthMonitor定时调用NameNode的HAServiceProtocol RPC接口(monitorHealth和getServiceStatus),监控NameNode的健康状态并向ZKFC反馈
- ActiveStandbyElector接收ZKFC的选举请求,通过Zookeeper自动完成namenode主备选举
-
主备选举过程:两个ZKFC通过各自ActiveStandbyElector发起NameNode的主备选举,这个过程利用Zookeeper的写一致性和临时节点机制实现。当发起一次主备选举时,ActiveStandbyElector会尝试在Zookeeper创建临时节点
/hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock,zookeeper写一致性保证最终只会有一个ActiveStandbyElector创建成功,- 创建成功的ActiveStandbyElector回调zkfc的回调方法,将对应的namenode切换成actice namenode,否则反之切换standy状态
- 不管选举成功还是失败所有ActiveStandbyElector都会在临时节点ActiveStandbyElectorLock上注册一个Watcher监听器,来监听这个节点的状态变化事件,HealthMonitor检测到NameNode状态异常时,通知对应ZKFC。
- ZKFC会调用 ActiveStandbyElector 方法,删除在Zookeeper上创建的临时节点ActiveStandbyElectorLock。Standby NameNode的ActiveStandbyElector注册的Watcher就会监听到此节点的 NodeDeleted事件。收到这个事件后,此ActiveStandbyElector发起主备选举,成功创建临时节点ActiveStandbyElectorLock,如果创建成功,则Standby NameNode被选举为Active NameNode(过程同上)
-
如何防止脑裂
-
脑裂:分布式中双主现象又称为脑裂,由于zookeeper的假死、长时间的垃圾回收或其他原因都可能导致双active namenode现象,此时的两个namenode都可以对外服务,无法保证数据一致性
-
隔离:这种情况是毁灭性的,必须通过自带的隔离机制。
-
ActiveStandbyElector成功创建ActiveStandbyElectorLock临时节点后,会创建另一个ActiveBreadCrumb持久节点
-
ActiveBreadCrumb持久节点保存了Active NameNode的地址信息
-
当Active NameNode在正常的状态下断开Zookeeper Session,会一并删除临时节点ActiveStandbyElectorLock、持久节点ActiveBreadCrumb
-
但是如果ActiveStandbyElector在异常的状态下关闭Zookeeper Session,那么持久节点ActiveBreadCrumb会保留下来(此时有可能由于active NameNode与ZooKeeper通信不畅导致,所以此NameNode还处于active状态)
-
当另一个NameNode要由standy变成active状态时,会发现上一个Active NameNode遗留下来的ActiveBreadCrumb节点,那么会回调ZKFailoverController的方法对旧的Active NameNode进行fencing
①首先ZKFC会尝试调用旧Active NameNode的HAServiceProtocol RPC接口的transitionToStandby方法,看能否将其状态切换为Standby
②如果transitionToStandby方法切换状态失败,那么就需要执行Hadoop自带的隔离措施,Hadoop目前主要提供两种隔离措施: sshfence:SSH to the Active NameNode and kill the process; shellfence:run an arbitrary shell command to fence the Active NameNode
③只有成功地fencing之后,选主成功的ActiveStandbyElector才会回调ZKFC的becomeActive方法将对应的NameNode切换为Active,开始对外提供服务
-
-
-
zookeeper的读写操作
zookeeper的读取操作:1、常见的读取操作,如ls /查看目录查询znode数据、读操作,客户端先与某个zk服务建立session,然后直接从zk服务器读取数据,并返回客户端,关闭session
zookeeper的写操作:
1、客户端向zk集群写入数据,如create /kkb;与一个follower 建立session连接,从follower01
2、follower将写请求转发给leader
3、leader收到消息后发出proposal提案,每个follower先记录下要创建/kbb
4、超过半数qourum同意提案,则leader提交commit提案,leader本地创建/kkb节点ZNode
5、leader通知所有follower,也commit提案;follower各自在本地创建/kkb⑥follower01响应client
全新集群选举: 原则:集群中过半数server启动后,才能选举出leader。
-
理解leader选举前,先了解几个概念
-
选举过程中,每个server需发出投票;投票信息vote信息结构为(sid, zxid)
全新集群,server1~3初始投票信息分别为:
server1 -> (1, 0) server2 -> (2, 0) server3 -> (3, 0)
-
leader选举公式:
server1 vote信息 (sid1,zxid1)
server2 vote信息 (sid2,zxid2)
①zxid大的server胜出;
②若zxid相等,再根据判断sid判断,sid大的胜出
-
-
选举leader流程:
假设按照ZK1、ZK2、ZK3的依次启动
- 启动ZK1后,投票给自己,vote信息(1,0),没有过半数,选举不出leader
- 再启动ZK2;ZK1和ZK2票投给自己及其他服务器;ZK1的投票为(1, 0),ZK2的投票为(2, 0)
- 处理投票。每个server将收到的多个投票做处理
- 如ZK1投给自己的票(1,0)与ZK2传过来的票(2,0)比较;
- 利用leader选举公式,因为zxid都为0,相等;所以判断sid最大值;2>1;ZK1更新自己的投票为(2, 0)
- ZK2也是如此逻辑,ZK2更新自己的投票为(2,0)
- 再次发起投票
- ZK1、ZK2上的投票都是(2,0)
- 发起投票后,ZK1上有一个自己的票(2,0)和一票来自ZK2的票(2,0),这两票都选ZK2为leader
- ZK2上有一个自己的票(2,0)和一票来自ZK1的票(2,0),这两票都选ZK2为leader
- 统计投票。server统计投票信息,是否有半数server投同一个服务器为leader;
- ZK2当选2票;多数
- 改变服务器状态。确定Leader后,各服务器更新自己的状态
- 更改ZK2状态从looking到leading,为Leader
- 更改ZK1状态从looking到following,为Follower
- 当K3启动时,发现已有Leader,不再选举,直接从LOOKING改为FOLLOWING
zab算法:待更新
zookeeper的状态同步:
完成leader选举后,zk就进入ZooKeeper之间状态同步过程
- leader构建NEWLEADER封包,包含leader中最大的zxid值;广播给其它follower
- follower收到后,如果自己的最大zxid小于leader的,则需要与leader状态同步;否则不需要
- leader给需要同步的每个follower创建LearnerHandler线程,负责数据同步请求
- leader主线程等待LearnHandler线程处理结果
- 只有多数follower完成同步,leader才开始对外服务,响应写请求
- 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的响应.