ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。
基于ZooKeeper的数据结构,Watcher,选举机制等可以实现数据的发布/订阅,软负载均衡,命名服务,统一配置管理,分布式锁,集群管理等功能。
Zookeeper 有三种运行模式:单机模式、伪集群模式和集群模式。
单机模式:这种模式一般适用于开发测试环境,一方面我们没有那么多机器资源,另外就是平时的开发调试并不需要极好的稳定性。
集群模式:一个 ZooKeeper 集群通常由一组机器组成,一般 3 台以上就可以组成一个可用的 ZooKeeper 集群了。组成 ZooKeeper 集群的每台机器都会在内存中维护当前的服务器状态,并且每台机器之间都会互相保持通信。
伪集群模式:这是一种特殊的集群模式,即集群的所有服务器都部署在一台机器上。当你手头上有一台比较好的机器,如果作为单机模式进行部署,就会浪费资源,这种情况下,ZooKeeper 允许你在一台机器上通过启动不同的端口来启动多个 ZooKeeper 服务实例,以此来以集群的特性来对外服务。
2. 为什么要使用zookeeper 2.1 利用zookeeper做全局配置管理在实际业务系统开发中,会通过配置属性让业务逻辑更加丰富。常采用的方案包括:
- 将配置文件写入数据库,通过读取数据库更新配置;
- 利用Redis缓存做中间数据存储;
- 通过zookeeper做配置管理,将配置属性写入zookeeper;
为什么选用zookeeper做配置中心呢?主要是当配置属性发生变化的时候,期望对应的业务系统能第一时间感知到,做对应属性的更新。zookeeper有强一致性保障,数据容错性更强;更重要的是zookeeper自带的watch机制,非常适合当有数据变化时触发,数据库和redis都无法直接提供此能力。
2.2 统一命名服务 2.3 分布式锁zookeeper致力于提供高性能、高可用、顺序一致性的分布式协调服务,保证数据最终一致性;主要实现包括:
-
高性能(简单的数据模型)
- 采用树形结构组织数据节点;
- 全量数据节点都存储在内存中;
- Follower 和 Observer 直接处理非事务请求;
- 高可用(构建集群)
- 半数以上机器存活,服务就能正常运行
- 自动进行 Leader 选举
- 顺序一致性(事务 *** 作的顺序)
- 每个事务请求,都会转发给 Leader 处理
- 每个事务,会分配全局唯一的递增id(zxid,64位:epoch + 自增 id)
- 最终一致性
- 事务提交后,一定会被半数以上的follower节点看到
ZooKeeper的数据结构,跟Unix文件系统非常类似,可以看做是一颗树,每个节点叫做ZNode。每一个节点可以通过路径来标识。结构如下:
每个节点在 Zookeeper 中叫做 Znode,并且其有一个唯一的路径标识;节点 Znode 可以包含数据和子节点。Znode分为四种类型:
- 临时(Ephemeral):客户端和服务端断开连接后,临时Znode节点会自动删除
- 临时顺序(EPHEMERAL_SEQUENTIAL): 创建节点的时候有顺序,会依次递增
- 持久(Persistent):客户端和服务端断开连接后,持久Znode节点不会删除。
- 持久顺序(PERSISTENT_SEQUENTIAL): 是持久性节点一种,只是创建节点的时候每个节点有序,遵循单调递增;
EPHEMERAL 类型的节点不能有子节点;
3.2 数据访问每个znode节点存储的数据都具有原子 *** 作性,也就是说这个节点的数据要么同时被写入,要么都没有写入;读数据的时候要么同时读到,要么都读不到。另外,每一个节点都拥有自己的ACL列表,限定了用户对目标节点可以执行的 *** 作,包括CREATE: 创建子节点的权限;READ: 获取节点数据和子节点列表的权限;WRITE: 更新节点数据的权限;DELETE: 删除子节点的权限;ADMIN: 设置节点ACL的权限。
注意:Znode 中的数据可以有多个版本,可以根据版本号访问对应节点数据。
4. zookeeper watcher监听机制ZooKeeper允许用户在指定节点上注册一些Watcher,当数据节点发生变化(数据内容发生变化或者子节点增减变化)的时候,ZooKeeper服务器会把这个变化的通知发送给感兴趣的客户端。Watcher 机制包括三个角色:客户端线程、客户端的 WatchManager 以及 ZooKeeper 服务器。具体过程有:
- 客户端向 ZooKeeper 服务器注册一个 Watcher 监听,
- 把这个监听信息存储到客户端的 WatchManager 中
- 当 ZooKeeper 中的节点发生变化时,会通知客户端,客户端会调用相应 Watcher 对象中的回调方法。
客户端回调 Watcher:
客户端在和 ZooKeeper 建立连接时,会启动 sendThread 和 eventThread 线程。sendThread 线程负责发送请求给服务端,同时也接收服务端发送过来的响应,当它判断到响应中的 XID 标识为 -1,便将它作为一个通知类型的响应,将响应中的信息进行序列化,交给 eventThread 线程处理。eventThread 会根据响应内容判断该通知对应的 Watcher 类型,从 ZKWatchManager 中取出所有相关的 Watcher,然后放到 waitingEvents 队列中,该队列时一个待处理 Watcher 的队列,eventThrad 每次从中取出一个 Watcher,然后进行串行同步处理,就是依次调用队列中 Watcher 的 process 的方法。
Watcher 特性总结:
- 一次性:不管是服务端还是客户端,当 Watcher 触发之后,就会将从本地内存中去除掉,如果还需要监听的话就需要反复注册。
- 轻量:客户端发送给服务器的请求中只是表明该请求是对哪个路径的监听,服务端给客户端响应也只是告诉它监听的节点或子列表发生变化了,具体的变化信息需要重新去服务端获取,这个轻量的设计使得网络带宽和服务器的压力大大减小了。
zookeeper中存在三种服务器角色,分别是Leader,Follower以及Observer,其中Observer仅仅作为一个监控协调者的作用,并不参与zookeeper对外提供服务以及zookeeper的选举。
服务器节点状态(每个节点有且只有一个状态):
- looking:寻找leader状态,当前集群没有leader,进入leader选举流程。
- following:跟随者状态,接受leading节点同步和指挥。
- leading:领导者状态。
- observing:观察者状态,表名当前服务器是observer。
在讲选举过程之前,先介绍几个关键概念:
- 事务编号zxid:zk每一次事务 *** 作都会产生唯一一个64位长度的zxid,其中前32是epoch,后32位是事务的计数序号;
- 纪元epoch:每一次选主完成后,leader都会产出自己的epoch;
- myid:zk节点在加入集群的时候,会配置节点服务器自由的一个ID号,默认是
/tmp/zookeeper/myid.
myid 在整个集群中,各个节点之间是不能重复的.
ZAB(Zookeeper Atomic Broadcast),是为zk专门设计的一种支持崩溃恢复的原子广播协议,是Zookeeper保证数据一致性的核心算法。Zab借鉴了Paxos算法,但又不像Paxos那样,是一种通用的分布式一致性算法。
ZAB协议包括两种基本的模式,分别是:
- 消息原子广播(保证数据一致性)
- 崩溃恢复(解决2pc算法的单点问题)
整个集群启动过程中,或者当 Leader 服务器出现网络中断、崩溃退出或重启等异常时,ZAB协议就会进入崩溃恢复模式,选举产生新的Leader。当选举产生了新的 Leader,同时集群中有过半的机器与该 Leader 服务器完成了状态同步(即数据同步)之后,ZAB协议就会退出崩溃恢复模式,进入消息广播模式。这时,如果有一台遵守ZAB协议的服务器加入集群,因为此时集群中已经存在一个Leader服务器在广播消息,那么该新加入的服务器自动进入恢复模式:找到Leader服务器,并且完成数据同步。同步完成后,作为新的Follower一起参与到消息广播流程中。
进一步拆分,在崩溃恢复拆分为选举阶段和数据同步阶段,消息广播对应消息广播阶段。
5.1 选举阶段服务启动期间的选举
- 每个节点都会对自己进行投票,然后把投票信息广播给集群中的其他节点
- 节点接收到其他节点的投票信息,然后和自己的投票进行比较,首先zxid较大的优先,如果zxid相同那么则会去选择myid更大者,此时大家都是LOOKING的状态
- 投票完成之后,开始统计投票信息,如果集群中过半的机器都选择了某个节点机器作为leader,那么选举结束
- 最后更新各个节点的状态,leader改为LEADING状态,follower改为FOLLOWING状态
服务运行期间的选举
如果选举出来的leader节点宕机了,那么运行期间就会重新进行leader的选举。
- leader宕机之后,非observer节点都会把自己的状态修改为LOOKING状态,然后重新进入选举流程
- 生成投票信息(myid,zxid),同样,第一轮的投票大家都会把票投给自己,然后把投票信息广播出去
- 接下来的流程和上面的选举是一样的,都会优先以zxid,然后选择myid,最后统计投票信息,修改节点状态,选举结束
整个同步过程主要是leader将自身proposal事务发送给从属于自身的大多数follower,在确认大多数follower接收到事务消息后,通知他们更新事务,保证follower和leader事务一致。
-
第一阶段 NewLeader:NewLeader 将新 epoch 和
S'
以NewLeader(e', S')
的消息形式发送给所有过半 (Quorum) 的 Follower。 S' 代表 NewLeader.history
。 -
第二阶段 Ack-LD:当 Follower 接收到
NewLeader(e', S')
消息后,- 如果 Follower 的 epoch 等于
e'
,确定是该主的子民,Follower 将会执行事务应用 *** 作,将接收S'
中的所有事务 Proposal,注意只是接收。 - 如果 Follower 的 epoch 不等于
e'
,即不是这一轮的 Follower,直接进入下一代循环。 - Leader 在接收到过半的 Follower 的 Ack-LD 消息后,发送 Commit 消息所有的 Follower,之后进入下一阶段即 Broadcast(消息广播)。
- 如果 Follower 的 epoch 等于
-
第三阶段 Commit-LD:在收到 Leader 的 Commit 消息后,按顺序依次调用
abdeliver(
处理) S'
的每一个事务,随后完成这一阶段。
数据同步包含3个主要值和4种形式:
- PeerLastZxid:Learner服务器最后处理的ZXID
- minCommittedLog:Leader提议缓存队列中最小ZXID
- maxCommittedLog:Leader提议缓存队列中最大ZXID
直接差异化同步 DIFF同步
如果PeerLastZxid在minCommittedLog和maxCommittedLog之间,说明Learner服务器还没有完全同步最新的数据。
- 首先Leader向Learner发送DIFF指令,代表开始差异化同步,然后把差异数据(从PeerLastZxid到maxCommittedLog之间的数据)提议proposal发送给Learner
- 发送完成之后发送一个NEWLEADER命令给Learner,同时Learner返回ACK表示已经完成了同步
- 接着等待集群中过半的Learner响应了ACK之后,就发送一个UPTODATE命令,Learner返回ACK,同步流程结束
先回滚再差异化同步 TRUNC+DIFF同步
Leader刚生成一个proposal,还没有来得及发送出去就宕机了,重新选举之后作为Follower,但是新的Leader没有这个proposal数据。
这个时候follower多出来的数据要先进行回滚,回滚到和新的leader最接近的PeerLastZxid的事务,leader会先发送一个TRUNC命令,然后再执行差异化DIFF同步。
仅trunc同步
如果follower的PeerLastZxid大于maxCommittedLog的场景,新的leader会仅仅发送trunc命令,将follower的数据回滚到与leader一致;
全量同步 SNAP同步
适用于两个场景:
- PeerLastZxid小于minCommittedLog
- Leader服务器上没有提议缓存队列,并且PeerLastZxid不等于Leader的最大ZXID
这两种场景下,Leader将会发送SNAP命令,把全量的数据都发送给Learner进行同步。
5.3 消息广播阶段消息广播流程:leader收到客户端的数据后,通知所有follower;follower收到事务处理消息后,保存事务记录到history中,并且返回响应给leader,告知已收到写事务;leader收到大多数follower的响应后,就会发起提交,通知所有follower去提交事务。然后重复上述流程,直到leader出现异常。
- 第一阶段 Propose:Leader 收到来自客户端新的事务请求后,会生成对应的事务 Proposal,并根据 zxid 的顺序(递增)向追随自己的所有 Follower 发送
P
>。 - 第二阶段 Ack:Follower 根据收到消息的次序来处理这些 Proposal,并追加到 H 中去,然后反馈给 Leader。
- 第三阶段 Commit:一旦 Follower 收到来自 Leader 的
Commit(epoch,
消息,将调用) abdeliver(
提交事务)
。需要注意的是,此时 Follower 必定提交了z' < z
之前的事务。
在进入消息广播阶段后,Leader 会为每一个 Follower 分配一个 FIFO 形式的队列进行通信,确保了同一时刻一个 Follower 只能和一个 Leader 保持同步,Leader 和 Follower 彼此之间通过心跳检测来感知。这个过程会一直持续,直到
- Leader 掉线了,Follower 的心跳包会超时,然后 Follower 进入 Looking 状态。
- Leader 没有过半的 Follower 追随了,Leader 自己进入 Looking 状态,追随它的 Follower 也会转为 Looking 状态。
zk可能出现数据不一致情况,分为3个场景来描述:
查询不一致
Zk的提案是过半成功即代表成功,如果查询的节点刚好不在过半范围内,查询出来的数据就会不一致。解决方案可以在读取前使用sync命令。
leader未发送proposal宕机
如上门数据同步所说,这个时候宕机leader的提案将会被抛弃;
leader发送proposal成功,发送commit前宕机
发送proposal成功了,但将要发送commit命令前宕机了,如果重新进行选举,还是会选择zxid最大的节点作为leader,这个时候的提案不会被抛弃,新leader选举后会被重新同步到其他节点。
7. zk的脑裂问题所谓的脑裂问题,就是在同一个集群环境,存在多个leader。ZK为了避免脑裂的问题,给出了一个规定:集群中存活的节点数必须要超过总节点数的半数才能继续提供服务,而集群服务器数量是基数台。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)