参考了:https://www.jianshu.com/p/84ad63127cd1
1、CAP定理
1.1 CAP定理
分布式系统最多只能满足C(Consistency一致性)、A(Available可用性)、P(Partition tolerance分区容错性)三个特性中的两个,不能同时满足三个。
1.2 最终一致性
在目前的分布式系统中,P(分区容错性)是必须要实现的,所以只能在C(一致性)和A(可用性)中选择。Zookeeper给出了C(一致性)和A(可用性)平衡方案,最终一致性。最终一致性指分布式系统中所有数据副本在经过一定时间同步后,最终能够达到一致的状态,也就是说Zookeeper提供的一致性是弱一致性。
首先zookeeper确保对znode树的每一个修改都会被复制到集合体中超过半数的机器上。那么就有可能有节点的数据不是最新的而被客户端访问到。并且会有一个时间点,在集群中是不一致的。也就是Zookeeper只保证最终一致性, 但是实时的一致性可以由客户端调用自己来保证,通过调用sync()方法.
1.3 ZooKeeper
ZooKeeper是一个高可用的分布式数据管理与协调框架。基于对ZAB算法的实现,该框架能够很好地保证分布式环境中数据的最终一致性。也是基于这样的特性,使得ZooKeeper成为了解决分布式一致性问题的利器。
2、ZooKeeper典型应用场景
2.1 分布式协调/通知
ZooKeeper中特有Watcher注册与异步通知机制,能够很好的实现分布式环境下不同机器,甚至不同系统之间的通知与协调,从而实现对数据变更的实时处理。使用方法通常是不同的客户端都对ZK上同一个ZNode进行注册,监听ZNode的变化(包括ZNode本身内容及子节点的),如果ZNode发生了变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并做出相应的处理。
ZK的分布式协调/通知,是一种通用的分布式系统机器间的通信方式。
2.2 Master选举
Master选举可以说是ZooKeeper最典型的应用场景了。比如HDFS中Active NameNode的选举、YARN中Active ResourceManager的选举和HBase中Active HMaster的选举等。
针对Master选举的需求,通常情况下,我们可以选择常见的关系型数据库中的主键特性来实现:希望成为Master的机器都向数据库中插入一条相同主键ID的记录,数据库会帮我们进行主键冲突检查,也就是说,只有一台机器能插入成功——那么,我们就认为向数据库中成功插入数据的客户端机器成为Master。
依靠关系型数据库的主键特性确实能够很好地保证在集群中选举出唯一的一个Master。但是,如果当前选举出的Master挂了,那么该如何处理?谁来告诉我Master挂了呢?显然,关系型数据库无法通知我们这个事件。但是,ZooKeeper可以做到!
利用ZooKeepr的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即ZooKeeper将会保证客户端无法创建一个已经存在的ZNode。也就是说,如果同时有多个客户端请求创建同一个临时节点,那么最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式环境中进行Master选举了。
成功创建该节点的客户端所在的机器就成为了Master。同时,其他没有成功创建该节点的客户端,都会在该节点上注册一个子节点变更的Watcher,用于监控当前Master机器是否存活,一旦发现当前的Master挂了,那么其他客户端将会重新进行Master选举。
这样就实现了Master的动态选举。
2.3 命名服务(Naming Service)
命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务,远程对象等等——这些我们都可以统称他们为名字(Name)。其中较为常见的就是一些分布式服务框架(如RPC、RMI)中的服务地址列表。通过在ZooKeepr里创建顺序节点,能够很容易创建一个全局唯一的路径,这个路径就可以作为一个名字。
ZooKeeper的命名服务即生成全局唯一的ID
2.4 心跳检测
机器间的心跳检测机制是指在分布式环境中,不同机器(或进程)之间需要检测到彼此是否在正常运行,例如A机器需要知道B机器是否正常运行。在传统的开发中,我们通常是通过主机直接是否可以相互PING通来判断,更复杂一点的话,则会通过在机器之间建立长连接,通过TCP连接固有的心跳检测机制来实现上层机器的心跳检测,这些都是非常常见的心跳检测方法。
下面来看看如何使用ZK来实现分布式机器(进程)间的心跳检测。
基于ZK的临时节点的特性,可以让不同的进程都在ZK的一个指定节点下创建临时子节点,不同的进程直接可以根据这个临时子节点来判断对应的进程是否存活。通过这种方式,检测和被检测系统直接并不需要直接相关联,而是通过ZK上的某个节点进行关联,大大减少了系统耦合。
2.5 工作进度汇报
在一个常见的任务分发系统中,通常任务被分发到不同的机器上执行后,需要实时地将自己的任务执行进度汇报给分发系统。这个时候就可以通过ZK来实现。在ZK上选择一个节点,每个任务客户端都在这个节点下面创建临时子节点,这样便可以实现两个功能:
通过判断临时节点是否存在来确定任务机器是否存活。
各个任务机器会实时地将自己的任务执行进度写到这个临时节点上去,以便中心系统能够实时地获取到任务的执行进度。
2.6 分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
分布式锁又分为排他锁和共享锁两种。
(1)排他锁
排他锁的核心是如何保证当前只有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到。
- 定义锁:ZooKeeper上的一个ZNode可以表示一个锁。例如/exclusive_lock/lock节点就可以被定义为一个锁。
- 获得锁:如上所说,把ZooKeeper上的一个ZNode看作是一个锁,获得锁就通过创建ZNode的方式来实现。所有客户端都去/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。ZooKeeper会保证在所有客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获得了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。
- 释放锁:因为/exclusive_lock/lock是一个临时节点,因此在以下两种情况下,都有可能释放锁:当前获得锁的客户端机器发生宕机或重启,那么该临时节点就会被删除,释放锁; 正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除,释放锁。无论在什么情况下移除了lock节点,ZooKeeper都会通知所有在/exclusive_lock节点上注册了节点变更Watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复“获取锁”过程。
(2)共享锁
可以多个事务同时获得一个对象的共享锁(同时读),有共享锁就不能再加排他锁(因为排他锁是写锁)
3、ZooKeeper在Hadoop集群中的应用
3.1 HDFS Namenode HA
(1)创建锁节点
在ZooKeeper上会有一个/hadoop-ha/cetc的锁节点,启动cetc是nameservices名称,
所有的Namenode在启动的时候,都会去竞争写一个Lock子节点:/hadoop-ha/cetc/ActiveBreadCrumb,该节点是临时节点。ZooKeepr能够为我们保证最终只有一个Namenode能够创建成功。创建成功的那个Namenode就切换为Active状态,没有成功的那些Namenode则切换为Standby状态。
[zk: localhost:2181(CONNECTED) 0] ls /hadoop-ha
[cetc]
[zk: localhost:2181(CONNECTED) 1] ls /hadoop-ha/cetc
[ActiveBreadCrumb, ActiveStandbyElectorLock]
[zk: localhost:2181(CONNECTED) 2] get /hadoop-ha/cetc/ActiveBreadCrumb
cetcnn1node1 �>(�>
cZxid = 0xb0000000a
ctime = Sat Jul 22 09:55:53 EDT 2017
mZxid = 0x4800000005
mtime = Sun Jan 28 03:36:18 EST 2018
pZxid = 0xb0000000a
cversion = 0
dataVersion = 64
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 24
numChildren = 0
[zk: localhost:2181(CONNECTED) 4]
从上面输出可以知道当前HDFS集群中node1是Active状态。
下面通过hdfs haadmin -getServiceState
命令验证
[root@node1 ~]# hdfs haadmin -getServiceState nn1
active
[root@node1 ~]# hdfs haadmin -getServiceState nn2
standby
[root@node1 ~]#
(2)注册Watcher监听
所有Standby状态的Namenode都会向/hadoop-ha/cetc/ActiveBreadCrumb节点注册一个节点变更的Watcher监听,利用临时节点的特性,能够快速感知到Active状态的Namenode的运行情况。
(3)主备切换
当Active状态的Namenoder出现诸如宕机或重启的异常情况时,其在ZooKeeper上连接的客户端会话就会失效,因此/hadoop-ha/cetc/ActiveBreadCrumb**临时节点**就会被删除。此时其余各个Standby状态的Namenoder就都会接收到来自ZooKeeper服务端的Watcher事件通知,然后会重复进行步骤1的操作。
下面通过停止node1上的Namenoder来验证主备切换:
- 停止nn1
[root@node1 ~]# hadoop-daemon.sh stop namenode
stopping namenode
[root@node1 ~]#
- 查看nn2状态
nn2已结切换到active状态
[root@node1 ~]# hdfs haadmin -getServiceState nn2
active
[root@node1 ~]#
- 查看临时锁节点
[zk: localhost:2181(CONNECTED) 17] get /hadoop-ha/cetc/ActiveBreadCrumb
cetcnn2node2 �>(�>
cZxid = 0xb0000000a
ctime = Sat Jul 22 09:55:53 EDT 2017
mZxid = 0x480000001a
mtime = Sun Jan 28 04:22:07 EST 2018
pZxid = 0xb0000000a
cversion = 0
dataVersion = 65
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 24
numChildren = 0
[zk: localhost:2181(CONNECTED) 18]
从上面输出信息可以知道,这是node2在zookeeper上创建的新的临时锁节点,mZxid和mtime值是新的。
(4)znode节点的状态信息补充说明
使用get命令获取指定节点的数据时, 同时也将返回该节点的状态信息, 称为Stat. 其包含如下字段:
- czxid. 节点创建时的zxid.
- mzxid. 节点最新一次更新发生时的zxid.
- ctime. 节点创建时的时间戳.
- mtime. 节点最新一次更新发生时的时间戳.
- dataVersion. 节点数据的更新次数.
- cversion. 其子节点的更新次数.
- aclVersion. 节点ACL(授权信息)的更新次数.
- ephemeralOwner. 如果该节点为ephemeral节点, ephemeralOwner值表示与该节点绑定的session id. 如果该节点不
- ephemeral节点, ephemeralOwner值为0. 至于什么是ephemeral节点, 请看后面的讲述.
- dataLength. 节点数据的字节数.
- numChildren. 子节点个数.
3.2 YARN ResourceManager HA
(1)创建锁节点
在ZooKeeper上会有一个/yarn-leader-election/yarn1的锁节点,所有的ResourceManager在启动的时候,都会去竞争写一个Lock子节点:/yarn-leader-election/yarn1/ActiveBreadCrumb,该节点是临时节点。ZooKeepr能够为我们保证最终只有一个ResourceManager能够创建成功。创建成功的那个ResourceManager就切换为Active状态,没有成功的那些ResourceManager则切换为Standby状态。
[zk: localhost:2181(CONNECTED) 9] ls /yarn-leader-election
[yarn1]
[zk: localhost:2181(CONNECTED) 10] ls /yarn-leader-election/yarn1
[ActiveBreadCrumb, ActiveStandbyElectorLock]
[zk: localhost:2181(CONNECTED) 11] get /yarn-leader-election/yarn1/ActiveBreadCrumb
yarn1rm2
cZxid = 0x290000001e
ctime = Fri Sep 08 11:10:17 EDT 2017
mZxid = 0x480000000d
mtime = Sun Jan 28 03:42:05 EST 2018
pZxid = 0x290000001e
cversion = 0
dataVersion = 11
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 12
numChildren = 0
[zk: localhost:2181(CONNECTED) 14]
注意,输出信息中有rm2,表示我们配置的第二个ResourceManager(也就是node3节点)状态是Active。
通过命令yarn rmadmin -getServiceState
验证
[root@node1 ~]# yarn rmadmin -getServiceState rm1
standby
[root@node1 ~]# yarn rmadmin -getServiceState rm2
active
[root@node1 ~]#
(2)注册Watcher监听
所有Standby状态的ResourceManager都会向/yarn-leader-election/yarn1/ActiveBreadCrumb节点注册一个节点变更的Watcher监听,利用临时节点的特性,能够快速感知到Active状态的ResourceManager的运行情况。
(3)主备切换
当Active状态的ResourceManager出现诸如宕机或重启的异常情况时,其在ZooKeeper上连接的客户端会话就会失效,因此/yarn-leader-election/yarn1/ActiveBreadCrumb节点就会被删除。此时其余各个Standby状态的ResourceManager就都会接收到来自ZooKeeper服务端的Watcher事件通知,然后会重复进行步骤1的操作。
- 停止rm2节点
[root@node3 ~]# yarn-daemon.sh stop resourcemanager
stopping resourcemanager
[root@node3 ~]#
- 查看rm1状态
[root@node1 ~]# yarn rmadmin -getServiceState rm1
active
[root@node1 ~]#
- 查看Zookeeper锁节点
[zk: localhost:2181(CONNECTED) 16] get /yarn-leader-election/yarn1/ActiveBreadCrumb
yarn1rm1
cZxid = 0x4800000017
ctime = Sun Jan 28 04:09:33 EST 2018
mZxid = 0x4800000017
mtime = Sun Jan 28 04:09:33 EST 2018
pZxid = 0x4800000017
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 12
numChildren = 0
[zk: localhost:2181(CONNECTED) 17]
发现这是一个rm1创建的新znode节点,mZxid和mtime值都变化了
以上就是利用ZooKeeper来实现ResourceManager的主备切换的过程,实现了ResourceManager的HA。
4、Zookeeper在HBase中的应用
HBase主要用ZooKeeper来实现HMaster选举与主备切换、系统容错、RootRegion管理、Region状态管理和分布式SplitWAL任务管理等。
4.1 HMaster选举与主备切换
HMaster选举与主备切换的原理和HDFS中NameNode及YARN中ResourceManager的HA原理相同。
[zk: localhost:2181(CONNECTED) 31] get /hbase/master
�master:16000D�dE���%PBUF
node1�}�ޛ�,�}
cZxid = 0x4800000029
ctime = Sun Jan 28 04:44:14 EST 2018
mZxid = 0x4800000029
mtime = Sun Jan 28 04:44:14 EST 2018
pZxid = 0x4800000029
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x2613be83d870004
dataLength = 53
numChildren = 0
[zk: localhost:2181(CONNECTED) 32]
说明当前HBase集群中node1是Active HMaster节点。
4.2 系统容错
当HBase启动时,每个RegionServer都会到ZooKeeper的/hbase/rs节点下创建一个信息节点(下文中,我们称该节点为”rs状态节点”),例如/hbase/rs/[Hostname],同时,HMaster会对这个节点注册监听。当某个 RegionServer 挂掉的时候,ZooKeeper会因为在一段时间内无法接受其心跳(即 Session 失效),而删除掉该 RegionServer 服务器对应的 rs 状态节点。与此同时,HMaster 则会接收到 ZooKeeper 的 NodeDelete 通知,从而感知到某个节点断开,并立即开始容错工作。
HBase为什么不直接让HMaster来负责RegionServer的监控呢?如果HMaster直接通过心跳机制等来管理RegionServer的状态,随着集群越来越大,HMaster的管理负担会越来越重,另外它自身也有挂掉的可能,因此数据还需要持久化。在这种情况下,ZooKeeper就成了理想的选择。
4.3 RootRegion管理
对应HBase集群来说,数据存储的位置信息是记录在元数据region,也就是RootRegion上的。每次客户端发起新的请求,需要知道数据的位置,就会去查询RootRegion,而RootRegion自身位置则是记录在ZooKeeper上的(默认情况下,是记录在ZooKeeper的/hbase/meta-region-server节点中)。当RootRegion发生变化,比如Region的手工移动、重新负载均衡或RootRegion所在服务器发生了故障等是,就能够通过ZooKeeper来感知到这一变化并做出一系列相应的容灾措施,从而保证客户端总是能够拿到正确的RootRegion信息。
[zk: localhost:2181(CONNECTED) 35] get /hbase/meta-region-server
�regionserver:16020�����PBUF
node3�}���,
cZxid = 0x480000004c
ctime = Sun Jan 28 04:44:35 EST 2018
mZxid = 0x480000004c
mtime = Sun Jan 28 04:44:35 EST 2018
pZxid = 0x480000004c
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 58
numChildren = 0
[zk: localhost:2181(CONNECTED) 36]