Zookeeper的基本概念和重要特性
Zookeeper是大数据生态圈中的重要组件,如果你做过相关开发的话,应该经常能看到它的身影。其由雅虎开源并成为Apache的顶级项目。用一句话对其进行定义就是:它是一套高吞吐的分布式协调系统。从中我们可以知道Zookeeper至少具有以下特点:
- 1.Zookeeper的主要作用是为分布式系统提供协调服务,包括但不限于:分布式锁,统一命名服务,配置管理,负载均衡,主控服务器选举以及主从切换等。
- 2.Zookeeper自身通常也以分布式形式存在。一个Zookeeper服务通常由多台服务器节点构成,只要其中超过一半的节点存活,Zookeeper即可正常对外提供服务,所以Zookeeper也暗含高可用的特性。客户端可以通过TCP协议连接至任意一个服务端节点请求Zookeeper集群提供服务,而集群内部如何通信以及如何保持分布式数据一致性等细节对客户端透明。如下图所示
- 3.Zookeeper是以高吞吐量为目标进行设计的,故而在读多写少的场合有非常好的性能表现。如下图所示
纵轴为每秒响应的客户端请求数,横轴为读请求所占百分比。从图中可以清晰的看到,随着读请求所占百分比的提高,Zookeeper的QPS也不断提高。
Zookeeper具有高吞吐特性的主要原因有以下几点:
1.Zookeeper集群的任意一个服务端节点都可以直接响应客户端的读请求(写请求会不一样些,下面会详谈),并且可以通过增加节点进行横向扩展。这是其吞吐量高的主要原因
2.Zookeeper将全量数据存储于内存中,从内存中读取数据不需要进行磁盘IO,速度要快得多。
3.Zookeeper放松了对分布式数据的强一致性要求,即不保证数据实时一致,允许分布式数据经过一个时间窗口达到最终一致,这也在一定程度上提高了其吞吐量。
而写请求,或者说事务请求,因为要进行不同服务器结点间状态的同步,一定程度上会影响其吞吐量。故而简单的增加Zookeeper的服务器节点数量,对其吞吐量的提升并不一定能起到正面效果。服务器节点增加,有利于提升读请求的吞吐量,但会延长服务器节点数据的同步时间,必须视具体情况在这两者之间取得一个平衡。
2. Zookeeper集群角色
在Zookeeper集群中,分别有Leader,Follower和Observer三种类型的服务器角色。
-
Leader
Leader服务器在整个正常运行期间有且仅有一台,集群会通过选举的方式选举出Leader服务器,由它同统一处理集群的事务性请求以及集群内各服务器的调度。 -
Follower
Follower的主要职责有以下几点:- 1.参与Leader选举投票
- 2.参与事务请求Proposal的投票
- 3.处理客户端非事务请求(读),并转发事务请求(写)给Leader服务器。
-
Observer
Observer是弱化版的Follower。其像Follower一样能够处理非事务也就是读请求,并转发事务请求给Leader服务器,但是其不参与任何形式的投票,不管是Leader选举投票还是事务请求Proposal的投票。引入这个角色主要是为了在不影响集群事务处理能力的前提下提升集群的非事务处理的吞吐量。
3. Zookeeper的数据模型
Zookeeper将数据存储于内存中,具体而言,Znode是存储数据的最小单元。而Znode被以层次化的结构进行组织,形容一棵树。其对外提供的视图类似于Unix文件系统。树的根Znode节点相当于Unix文件系统的根路径。正如Unix中目录下可以有子目录一样,Znode结点下也可以挂载子结点,最终形成如下所示结构。
以文件系统进行类比的话,Znode天然具有目录和文件两重属性:即Znode既可以当做文件往里面写东西,又可以当做目录在下面挂载其他Znode。当然,由于Znode具有不同的类型,后半部分并不完全准确。
3.1 Znode的类型
Znode按其生命周期的长短可以分为持久结点(PERSISTENT)和临时结点(EPHEMERAL);在创建时还可选择是否由Zookeeper服务端在其路径后添加一串序号用来区分同一个父结点下多个结点创建的先后顺序。
经过组合就有以下4种Znode结点类型
-
1.持久结点(PERSISTENT)
最常见的Znode类型,一旦创建将在一直存在于服务端,除非客户端通过删除操作进行删除。持久结点下可以创建子结点。 -
2.持久顺序结点(PERSISTENT_SEQUENTIAL)
在具有持久结点基本特性的基础上,会通过在结点路径后缀一串序号来区分多个子结点创建的先后顺序。这工作由Zookeeper服务端自动给我们做,只要在创建Znode时指定结点类型为该类型。
-
3.临时结点(EPHEMERAL)
临时结点的生命周期和客户端会话保持一致。客户端段会话存在的话临时结点也存在,客户端会话断开则临时结点会自动被服务端删除。临时结点下不能创建子结点。 -
4.临时顺序结点(EPHEMERAL_SEQUENTIAL)
具有临时结点的基本特性,又有顺序性。
3.2 Znode的结构
Znode结构主要由存储于其中的数据信息和状态信息两部分构成,通过get 命令获取一个Znode结点的信息如下
第一行存储的是ZNode的数据信息,从cZxid开始就是Znode的状态信息
Znode的状态信息比较多,挑几个比较重要的讲
-
czxid:
即Created ZXID,表示创建该Znode结点的事务ID -
mzxid:
即Modified ZXID,表示最后一次更新该结点的事务ID -
version
该Znode结点的版本号。每个Znode结点被创建时版本号都为0,每更新一次都会导致版本号加1,即使更新前后Znode存储的值没有变化版本号也会加1。version值可以形象的理解为Znode结点被更新的次数。Znode状态信息中的版本号信息,使得服务端可以对多个客户端对同一个Znode的更新操作做并发控制。整个过程和java中的CAS有点像,是一种乐观锁的并发控制策略,而version值起到了冲突检测的功能。客户端拿到Znode的version信息,并在更新时附上这个version信息,服务端在更新Znode时必须必须比较客户端的version和Znode的实际version,只有这两个version一致时才会进行修改。
4. Zookeeper的事件监听机制
Zookeeper中可以通过Watcher来实现事件监听机制。客户端可以向服务端注册Watcher用以监听某些事件,一旦该事件发生,服务端即会向客户端发送一个通知。其主要工作流程如下图所示
具体而言,Watcher是Zookeeper原生API中提供的事件监听接口,用户要实现事件监听必须实现该接口并重写process(WatchedEvent event)方法,该方法定义了客户端在接收到服务端事件通知后的回调逻辑。究竟服务端的什么事件可以被监听?按通知状态划分有SyncConnected,Disconnected,Expired,AuthFailed等好多种,这里主要讲下SyncConnected状态下的几种事件类型:
-
Node(-1)
客户端与服务端成功建立会话 -
NodeCreated(1)
Watcher监听的对应Znode被创建 -
NodeDeleted(2)
Watcher监听的Znode被删除 -
NodeDataChanged(3)
Watcher监听的Znode的数据内容被改变,注意即使变更前后的数据内容完全一样也会触发该事件,或者理解成该事件的触发条件是Znode的版本号变更也没问题 -
NodeChildrenChanged(4)
Watcher监听的对应Znode的子结点发生变更
Zookeeper的事件监听机制有以下特性:
-
1.当监听器监听的事件被触发,服务端会发送通知给客户端,但通知信息中不包括事件的具体内容。以监听ZNode结点数据变化为例,当Znode的数据被改变,客户端会收到事件类型为NodeDataChanged的通知,但该Znode的数据改变成了什么客户端无法从通知中获取,需要客户端在收到通知后手动去获取。
-
2.Watcher是一次性的。一旦被触发将会失效。如果需要反复进行监听就需要反复进行注册。这么设计是为了减轻服务端的压力,但是对开发者而言却是相当不友好,不过不用着急,可以通过一些Zookeeper的开源客户端轻松实现对某一事件的永久监听。
5. Zookeeper如何保证分布式数据一致性——ZAB协议
Zookeeper采用ZAB(Zookeeper Atomic Broadcast)协议来保证分布式数据一致性。ZAB并不是一种通用的分布式一致性算法,而是一种专为Zookeeper设计的崩溃可恢复的原子消息广播算法。ZAB协议包括两种基本模式:崩溃恢复模式和消息广播模式。崩溃恢复模式主要用来在集群启动过程,或者Leader服务器崩溃退出后进行新的Leader服务器的选举以及数据同步;消息广播模式主要用来进行事务请求的处理。下面就从这两个方面来介绍
5.1 事务请求的处理流程
ZAB协议的核心是定义了对事务请求的处理方式,整个过程可以概括如下:
- 1.所有的事务请求都交由集群的Leader服务器来处理,Leader服务器会将一个事务请求转换成一个Proposal(提议),并为其生成一个全局递增的唯一ID,这个ID就是事务ID,即ZXID,Leader服务器对Proposal是按其ZXID的先后顺序来进行排序和处理的。
- 2.之后Leader服务器会将Proposal放入每个Follower对应的队列中(Leader会为每个Follower分配一个单独的队列),并以FIFO的方式发送给Follower服务器。
- 3.Follower服务器接收到事务Proposal后,首先以事务日志的方式写入本地磁盘,并且在成功后返回Leader服务器一个ACK响应。
- 4.Leader服务器只要收到过半Follower的ACK响应,就会广播一个Commit消息给Follower以通知其进行Proposal的提交,同时Leader自身也会完成Proposal的提交。
整个过程如下图所示
5.2 Leader服务器的选举流程
当集群中不存在Leader服务器时集群会进行Leader服务器的选举,这通常存在于两种情况:1.集群刚启动时 2.集群运行时,但Leader服务器因故退出。集群中的服务器会向其他所有的Follower服务器发送消息,这个消息可以形象化的称之为选票,选票主要由两个信息组成,所推举的Leader服务器的ID(即配置在myid文件中的数字),以及该服务器的事务ID,事务表示对服务器状态变更的操作,一个服务器的事务ID越大,则其数据越新。整个过程如下所述:
- 1.Follower服务器投出选票(SID,ZXID),第一次每个Follower都会推选自己为Leader服务器,也就是说每个Follower第一次投出的选票是自己的服务器ID和事务ID。
- 2.每个Follower都会接收到来自于其他Follower的选票,它会基于如下规则重新生成一张选票:比较收到的选票和自己的ZXID的大小,选取其中最大的;若ZXID一样则选取SID即服务器ID最大的。最终每个服务器都会重新生成一张选票,并将该选票投出去。
这样经过多轮投票后,如果某一台服务器得到了超过半数的选票,则其将当前选为Leader。由以上分析可知,Zookeeper集群对Leader服务器的选择具有偏向性,偏向于那些ZXID更大,即数据更新的机器。
整个过程如下图所示
6. Zookeeper如何进行服务器故障的容错
Zookeeper通过事务日志和数据快照来避免因为服务器故障导致的数据丢失。
- 事务日志是指服务器在更新内存数据前先将事务操作以日志的方式写入磁盘,Leader和Follower服务器都会记录事务日志。
- 数据快照是指周期性通过深度遍历的方式将内存中的树形结构数据转入外存快照中。但要注意这种快照是"模糊"的,因为可能在做快照时内存数据发生了变化。但是因为Zookeeper本身对事务操作进行了幂等性保证,故在将快照加载进内存后会通过执行事务日志的方式来讲数据恢复到最新状态。
7. 参考资料
- 《从PAXOS到ZOOKEEPER分布式一致性原理与实践》
- 《大数据日知录》