Zookeeper介绍

Zookeeper简单介绍

Zookeeper功能

在我了解,Zookeeper主要提供两个功能:

提供高可用分布式一致性的树状存储服务 – Zookeepr Server

树状存储

Zookeeper提供了树状存储结构,通过Client根据树节点的路径对节点进行增删改查以及修改节点内数据等操作,Client提供的API很完善。

高可用

Zookeeper提供的树存储服务是高可用的,在部署有Zookeeper机器的集群中只要满足n/2 + 1个Zookeeper Server(Leader + Follower不包括Observer)状态正常,服务就可用,解决了分布式场景单点挂可靠存储即不可用的问题。

分布式一致性

Zookeeper是基于Paxos算法的工程实现,保证了各个Server节点的状态一致,Client将相同查询请求发给各个Server时,都可以拿到一致的结果。

提供结点变更通知服务 – Zookeepr Client

Client初始化过程以及其提供的API都可以注册Watcher,在调用Client的API给一个树节点注册Watcher后,Client会调用Server说明该树节点被这个Client监控,在下一次该节点变更成功后,Server会将这个节点的变更事件发送给对应注册Watcher的Client,Client根据事件描述的节点选择对应的Watcher调用其process方法进行预先定义的业务操作。

分布式事物一致性保证 – 两阶段提交

在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈决定各参与者是否要提交操作还是回滚操作。

角色

发起者

一个分布式事物的发起方。

协调者

处理发起方发起的分布式事物,并请求对应参与方执行该事物,分布式事物的控制着,判定事物成功或失败。

参与者

一个分布式事物的参与方。

一次两阶段提交流程

1.Activity

发起者向协调者发起一次分布式事物请求,记作Activity。

2.Action

协调者根据发起的Activity,拆分成数个Action发送给对应的参与者。

3.Ready/Fail

参与者收到Action进行对应操作,针对Action返回给协调者成功/失败。

4.Success/Fail

协调者针对参与者Action的结果返回给发起者该Activity是成功/失败。

5.Commit/Rollback

协调者针对Activity的结果成功/失败向参与者循环定时发送Commit/Rollback消息,直到参与者返回Ack,也就是说一阶段成功,二阶段必须成功。

6.Ack

参与者对Commit/Rollback消息处理后返回协调者Ack。

典型实现


两阶段提交的流程典型实现就是协调者基于DB插入/更新记录来保证事物状态。基于Activty表及Action表的记录来保证一次分布式事物中的各个阶段的状态。

Activity_table

协调者基于发起者发起Activity请求在Activity_table表中插入记录,协调者Recover机制也会回查发起者更新记录。

Action_table

Action_table表中数据记录维护Action状态,协调者发起Action前向Action_table插入记录,参与者返回Action结果及二阶段Commit/Rollback、Ack都会更新Action_table对应数据。

缺陷

大部分两阶段分布式事物框架维护事物的状态都是基于DB完成的,但是使用DB存储的可用性较低,DB挂则分布式事物框架不可用。基于这一缺陷,就引出了Paxos算法,将协调者存储状态的DB拆分成了多点。

Paxos算法

Paxos算法-两阶段提交的分布式优化版

在我理解,两阶段提交具有的单点问题,Paxos算法已经解决了。不严谨的描述一下Paxos算法,按照我自己的理解,在发起一次事物提议(事物)时,将决定这个提议(事物)的单点拆成了多点,多点基于少数服从多数的原则来确认当前的提议是否有效;多点中的一员接收到过期的提议时,直接拒绝。当多点中大部分成员通过提议(事物)时,执行该提议(事物)。

角色

Proposer提议者

proposers提出提案,提案信息包括提案编号和提议的value。

Acceptor决议者

acceptor收到提案后可以接受(accept)提案,若提案获得多数acceptors的接受,则称该提案被批准(chosen)。

Learner学习者

learners只能“学习”被批准的提案。

决议的提出与批准


针对一种特定场景描述一下算法的实现过程,当proposer1和propose2同时发出一条编号为n的提案。
acceptor1 acceptor2先收到proposer1的提案并回复有效,acceptor3先收到proposer2的n号提议,则返回给proposer1无效。
这样proposer1发起的编号为n提案2条有效1条无效,proposer2发起的编号为n的提案2条无效1条有效,那么proposer1的提案被决议者的多数派通过,则proposer1发起广播给acceptor以及learner执行n号提案。
特定场景下,proposer2则将自己的提案改为n+1号继续发起投票。
可以看出,当只有一个proprose发出请求时,整个过程就回退到了传统的两阶段提交。

特定场景的异常-活锁

上述过程既可以容忍proposer提案后故障,也可以容忍少于一半的acceptors故障。但有一种情况,同一时间内多个proposer均在提案出现了竞争的情况,这时很难有proposer的提案得票数超过一半,不断的在重复投票过程,导致效率低下。
当一proposer提交的proposal被拒绝时,可能是因为acceptor promise了更大编号的proposal,因此proposer提高编号继续提交。 如果2个proposer都发现自己的编号过低转而提出更高编号的proposal,会导致死循环,也称为活锁。

选主 Proposer’s Leader

针对上述竞争/活锁的情况,又提出了优化版本。针对proposer这个角色选出Leader,其他proposer发起提案是把请求统一发送给Leader,由Leader对提议统一编号并统一发给各个Acceptors,每个proposer都可以胜任Leader这一角色,当Leader挂之后,可以根据特定规则选出新Leader。如下图所示。

Paxos与两阶段提交的关系

假如leader只提交一个proposal给acceptor的简单情况:
发送prepared给多数派acceptor
接收多数派的响应
发送accept给多数派使其批准对应的value
其实就是一个二段提交问题,整个paxos算法可以看作是多个交叉执行而又相互影响的二段提交算法。

Paxos算法工程实现 – Zookeeper

Zookeeper-Server类型

Zookeeper-Server分为三个不同的类型,分别为:Leader,Follower,Observer。

Leader

对应Paxos算法中 Proposer’s Leader 和 Acceptor 角色。
负责处理ZK-Client,Follower针对树状存储的事物操作请求。
针对事物请求发送投票,判定该事物是否commit,向Leader,Follower,Observer发送commit命令。
处理Leader(自己)的事物commit请求,并向Client提供树状存储的非事物查询服务。

Follower

对应Paxos算法中 Proposer 和 Acceptor 角色。
负责接收Client针对树状存储的事物请求并转发给Leader。
针对Leader的事物操作投票。
处理Leader的事物commit请求,并向Client提供树状存储的非事物查询服务。

Observer

对应Paxos算法中 Learner 角色。
处理Leader的事物commit请求,并向Client提供树状存储的非事物查询服务。

一次事物发起后的具体流程

1.Tx Request

ZK Client发送一个针对树状存储修改操作的事物请求。

2.Tx Request To Leader

Follower转发该事物请求给Leader。

3.proposal zxid

Leader针对该事物生成唯一的zxid,并向Follower发起改zxid的proposal。

4.proposal zxid ack

Follower收到proposal后,将zxid放入pendingQueue中,并通知Leader该proposal通过。

5.Commit

针对这次proposal过半Follower(包括Leader)后,向集群广播Commit消息,Follower收到commit消息后执行事物,修改树。

6.return/send tx event

对应Follower返回给发起请求的Client/如果有Client监控对应修改的节点,则发送给对应Client修改事件。

选主的实现 (fastLeader)

名词定义

Serverid:代码里又叫myId,在zoo.cfg文件里面配置的,代表当前Zookeeper Server的ID。
zxid:上文有提,即事物ID,zxid越大,代表该事物执行的时间点离现在越近,事物越新。
Epoch:选举代的概念,一轮选举窗口称为一代,按照选举轮数++。
Server状态:LOOKING,FOLLOWING,OBSERVING,LEADING。除Observer外,Server默认启动为LOOKING状态,选主成功后Leader状态为LEADING,Follower为FOLLOWING。

代码流程

Zookeeper – Observer

Zookeeper Observer是Zookeeper Server的一种,Observer在进行事物操作时,Leader不会发送投票,只会通知Observer具体需要执行的事物。Observer仍然会维护树结构,并可以触发链接的Client对应节点修改的事件。

如何配置

在zoo.cfg配置时,在Server后面在配置”:observer”即可。

有何作用

我个人理解,Observer主要的作用是在多数据中心,或多机房部署时,可能适当提高效率。在多数据中心(机房场景下)如果需要共享状态,则需要跨机房部署Zookeeper-Client,可能的做法如下:

A集群维护Zookeeper服务,B集群、C集群通过Client跨集群连接。这样的问题在于当A集群整体不可用,如出现掉电等情况,那么可能导致B集群、C集群也都不可用。
为了解决上述情况,可能的做法是将Zookeeper-Server打散均匀部署在A、B、C三个集群内,如下图所示。

这种架构的优点是容灾能力强,但是缺点也很明显,每次执行事务操作修改数据都会涉及跨集群投票、确认、commit相关操作,跨集群请求延时较高,所以处理事务效率很低。
Observer相当于在两者中取折中方案,如图:

这种方案的对比上一种,写效率大大提高。当A集群不可用时,B、C集群虽然不能向Zookeeper写入数据但是历史数据仍可通过Observer读取,不可用的可能性能降低。