ZooKeeper第四天

3. 单节点源码解读

3.1.客户端源码

3.1.1. 总体流程

启动客户端 zkCli.sh 文件里面的配置:

实际运行:

Main 方法流程:

1. new ZooKeeperMain 对象

2. 调用 run()方法

在 ZookeeperMain 的构造方法里面,重点是:

public ZooKeeperMain(String args[]) throws IOException, InterruptedException {

     cl.parseOptions(args);

    System.out.println("Connecting to " + cl.getOption("server"));

    //连接上 ZK

    connectToZK(cl.getOption("server"));

}

protected void connectToZK(String newHost) throws InterruptedException, IOException {

    if (zk != null && zk.getState().isAlive()) {

    zk.close();

}

    host = newHost;

    boolean readOnly = cl.getOption("readonly") != null;

    zk = new ZooKeeper(host, Integer.parseInt(cl.getOption("timeout")), new MyWatcher(), readOnly);

}

最终在 connectToZK 方法里面也就是使用原生的 Zk 客户端进行连接的。

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)

throws IOException{

    LOG.info("Initiating client connection, connectString=" + connectString+ " sessionTimeout=" +     sessionTimeout + " watcher=" + watcher);

    watchManager.defaultWatcher = watcher;

    ConnectStringParser connectStringParser = new ConnectStringParser(connectString);

    HostProvider hostProvider = new StaticHostProvider(connectStringParser.getServerAddresses());

    cnxn = new ClientCnxn(connectStringParser.getChrootPath(), hostProvider, sessionTimeout, this,     watchManager, //获得和服务端连接的对象

    getClientCnxnSocket(), canBeReadOnly);

    //调用 start()

    cnxn.start();

}

public void start() {

    sendThread.start();

    eventThread.start();

}


3.1.2. 开启 SendThread 线程

org.apache.zookeeper.ClientCnxn.SendThread#run

3.1.3. 开启 EventThread

org.apache.zookeeper.ClientCnxn.EventThread.run

3.1.4. 总结:


3.2.服务端源码(单机)

3.2.1. 总体流程

3.2.2. 具体处理流程


4. Zookeeper 高级

4.1.一致性协议概述

前面已经讨论过,在分布式环境下,有很多不确定性因素,故障随时都回发生,也讲了 CAP理论,BASE 理论;

我们希望达到,在分布式环境下能搭建一个高可用的,且数据高一致性的服务,目标是这样,但 CAP 理论告诉我们要达到这样的理想环境是不可能的。这三者最多完全满足 2 个。

在这个前提下,P(分区容错性)是必然要满足的,因为毕竟是分布式,不能把所有的应用全放到一个服务器里面,这样服务器是吃不消的,而且也存在单点故障问题。

所以,只能从一致性和可用性中找平衡。怎么个平衡法?在这种环境下出现了 BASE 理论:即使无法做到强一致性,但分布式系统可以根据自己的业务特点,采用适当的方式来使系统达到最终的一致性;

BASE 由 Basically Avaliable 基本可用、Soft state 软状态、Eventually consistent 最终一致性组成,一句话概括就是:平时系统要求是基本可用,除开成功失败,运行有可容忍的延迟状态,但是,无论如何经过一段时间的延迟后系统最终必须达成数据是一致的。

其实可能发现不管是 CAP 理论,还是 BASE 理论,他们都是理论,这些理论是需要算法来实现的,今天讲的 2PC、3PC、Paxos 算法,ZAB 算法就是干这事情。

所以今天要讲的这些的前提一定是分布式,解决的问题全部都是在分布式环境下,怎么让系统尽可能的高可用,而且数据能最终能达到一致。

4.1.1. 两阶段提交 two-phase

首先来看下 2PC,翻译过来叫两阶段提交算法,它本身是一致强一致性算法,所以很适合用作数据库的分布式事务,其实数据库的经常用到的 TCC 本身就是一种 2PC.。回想下数据库的事务,数据库不管是 MySQL 还是 MSSql,本身都提供的很完善的事务支持。

MySQL 后面学分表分库的时候会讲到在 innodb 存储引擎,对数据库的修改都会写到 undo和 redo 中,不只是数据库,很多需要事务支持的都会用到这个思路。

对一条数据的修改操作首先写 undo 日志,记录的数据原来的样子,接下来执行事务修改操作,把数据写到 redo 日志里面,万一捅娄子,事务失败了,可从 undo 里面回复数据。

不只是数据库,在很多企业里面,比如华为等提交数据库修改都会要求这样,你要新增一个字段,首先要把修改数据库的字段 SQL 提交给 DBA(redo),这不够,还需要把你删除提交字段,把数据还原成你修改之前的语句也一并提交者叫(undo)。

数据库通过 undo 与 redo 能保证数据的强一致性,要解决分布式事务的前提就是当个节点是支持事务的。

这在个前提下,2pc 借鉴这失效,首先把整个分布式事务分两节点,首先第一阶段叫准备节点,事务的请求都发送给一个个的资源,这里的资源可以是数据库,也可以是其他支持事务的框架,他们会分别执行自己的事务,写日志到 undo 与 redo,但是不提交事务。

当事务管理器收到了所以资源的反馈,事务都执行没报错后,事务管理器再发送 commit 指令让资源把事务提交,一旦发现任何一个资源在准备阶段没有执行成功,事务管理器会发送rollback,让所有的资源都回滚。这就是 2pc,非常非常简单。

说他是强一致性的是他需要保证任何一个资源都成功,整个分布式事务才成功。

4.1.1.1. 优点:

优点:原理简单,实现方便

4.1.1.2. 缺点:

缺点:同步阻塞,单点问题,数据不一致,容错性不好

4.1.1.2.1. 同步阻塞

在二阶段提交的过程中,所有的节点都在等待其他节点的响应,无法进行其他操作。这种同步阻塞极大的限制了分布式系统的性能。

4.1.1.2.2. 单点问题

协调者在整个二阶段提交过程中很重要,如果协调者在提交阶段出现问题,那么整个流程将无法运转。更重要的是,其他参与者将会处于一直锁定事务资源的状态中,而无法继续完成事务操作。

4.1.1.2.3. 数据不一致

假设当协调者向所有的参与者发送 commit 请求之后,发生了局部网络异常,或者是协调者在尚未发送完所有 commit 请求之前自身发生了崩溃,导致最终只有部分参与者收到了commit 请求。这将导致严重的数据不一致问题。

4.1.1.2.4. 容错性不好

二阶段提交协议没有设计较为完善的容错机制,任意一个节点是失败都会导致整个事务的失败。

4.1.2. 三阶段提交 three-phase commit (3PC)

由于二阶段提交存在着诸如同步阻塞、单点问题,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。

4.1.2.1. 第一阶段 canCommit

确认所有的资源是否都是健康、在线的,以约女孩举例,你会打个电话问下她是不是在家,而且可以约个会。如果女孩有空,你在去约她。

就因为有了这一阶段,大大的减少了 2 段提交的阻塞时间,在 2 段提交,如果有 3 个数据库, 恰恰第三个数据库出现问题,其他两个都会执行耗费时间的事务操作,到第三个却发现连接不上,3 段优化了这种情况。

4.1.2.2. 第二阶段 PreCommit

如果所有服务都 ok,可以接收事务请求,这一阶段就可以执行事务了,这时候也是每个资源都回写 redo 与 undo 日志,事务执行成功,返回 ack(yes),否则返回 no。

4.1.2.3. 第三阶段 doCommit

这阶段和前面说的 2 阶段提交大同小异,这个时候协调者发现所有提交者事务提交者事务都正常执行后,给所有资源发送 commit 指令。和二阶段提交有所不同的是,他要求所有事务在协调者出现问题,没给资源发送 commit 指令的时候,三阶段提交算法要求资源在一段时间超时后回默认提交做 commit 操作。

这样的要求就减少了前面说的单点故障,万一事务管理器出现问题,事务也回提交。但回顾整个过程,不管是 2pc,还是 3pc,同步阻塞,单点故障,容错机制不完善这些问题都没本质上得到解决,尤其是前面说得数据一致性问题,反而更糟糕了。所有数据库的分布式事务一般都是二阶段提交,而者三阶段的思想更多的被借鉴扩散成其他的算法。

4.1.3. Paxos 算法

这个算法还是有点难度的,本身这算法的提出者莱斯利·兰伯特在前面几篇论文中都不是以严谨的数学公式进行的。其实这个 paxos 算法也分成两阶段:首先就是这个图的 2 个角色,提议者与接收者。

4.1.3.1. 第一阶段

提议者对接收者吼了一嗓子,我有个事情要告诉你们,当然这里接受者不只一个,它也是个分布式集群;相当于星期一开早会,可耻的领导吼了句:“要开会了啊,我要公布一个编号为 001 的提案,收到请回复”。

这个时候领导就会等着,等员工回复 1“好的”,如果回复的数目超过一半,就会进行下一步。如果由于某些原因(接收者死机,网络问题,本身业务问题),导致通过的协议未超过一半,这个时候的领导又会再吼一嗓子,当然气势没那凶残:“好了,怕了你们了,我要公布一个新的编号未 002 的提案,收到请回复 1”。

4.1.3.2. 第二阶段

接下来到第二阶段,领导苦口婆心的把你们叫来开会了,今天编号 002 提案的内容是:“由于项目紧张,今天加班到 12 点,同意的请举手”这个时候如果绝大多少的接收者都同意,那么好,议案就这么决定了,如果员工反对或者直接夺门而去,那么领导又只能从第一个阶段开始:“大哥,大姐们,我有个新的提案 003,快回会议室吧。。”

4.1.3.3. 详细说明:

【注意:不懂没事,记住上面那简单情况就好,面试足够】

上面那个故事描绘的是个苦逼的领导和凶神恶煞的员工之间的斗争,通过这个故事你们起码要懂 paxos 协议的流程是什么样的(paxos 的核心就是少数服从多数)。

上面的故事有两个问题:

苦逼的领导(单点问题):有这一帮凶残的下属,这领导要不可能被气死,要不也会辞职,这是单点问题。

凶神恶煞的下属(一致性问题):如果员工一直都拒绝,故意和领导抬杆,最终要产生一个一致性的解决方案是不可能的。

所以 paxos 协议肯定不会只有一个提议者,作为下属的员工也不会那么强势;

协议要求:如果接收者没有收到过提案编号,他必须接受第一个提案编号;如果接收者没有收到过其他协议,他必须接受第一个协议。

举一个例子:有 2 个 Proposer(老板,老板之间是竞争关系)和 3 个 Acceptor(政府官员):

4.1.3.3.1. 阶段一

1.现在需要对一项议题来进行 paxos 过程,议题是“A 项目我要中标!”,这里的“我”指每个带着他的秘书 Proposer 的 Client 老板。

2.Proposer 当然听老板的话了,赶紧带着议题和现金去找 Acceptor 政府官员。

3.作为政府官员,当然想谁给的钱多就把项目给谁。

4.Proposer-1 小姐带着现金同时找到了 Acceptor-1~Acceptor-3 官员,1 与 2 号官员分别收取了 10 比特币,找到第 3 号官员时,没想到遭到了 3 号官员的鄙视,3 号官员告诉她,Proposer-2给了 11 比特币。不过没关系,Proposer-1 已经得到了 1,2 两个官员的认可,形成了多数派(如果没有形成多数派,Proposer-1 会去银行提款在来找官员们给每人 20 比特币,这个过程一直重复每次+10 比特币,直到多数派的形成),满意的找老板复命去了,但是此时 Proposer-2保镖找到了 1,2 号官员,分别给了他们 11 比特币,1,2 号官员的态度立刻转变,都说 Proposer-2的老板懂事,这下子 Proposer-2 放心了,搞定了 3 个官员,找老板复命去了,当然这个过程是第一阶段提交,只是官员们初步接受贿赂而已。故事中的比特币是编号,议题是 value。 这个过程保证了在某一时刻,某一个proposer 的议题会形成一个多数派进行初步支持。

4.1.3.3.2. 阶段二

5. 现在进入第二阶段提交,现在 proposer-1 小姐使用分身术(多线程并发)分了 3 个自己分别去找 3 位官员,最先找到了 1 号官员签合同,遭到了 1 号官员的鄙视,1 号官员告诉他proposer-2 先生给了他 11 比特币,因为上一条规则的性质 proposer-1 小姐知道 proposer-2第一阶段在她之后又形成了多数派(至少有 2 位官员的赃款被更新了);此时她赶紧去提款准备重新贿赂这 3 个官员(重新进入第一阶段),每人 20 比特币。刚给 1 号官员 20 比特币, 1号官员很高兴初步接受了议题,还没来得及见到 2、3 号官员的时候这时 proposer-2 先生也使用分身术分别找 3 位官员(注意这里是 proposer-2 的第二阶段),被第 1 号官员拒绝了告诉他收到了 20 比特币,第 2、3 号官员顺利签了合同,这时 2、3 号官员记录 client-2 老板用了 11 比特币中标,因为形成了多数派,所以最终接受了 Client2 老板中标这个议题,对于 proposer-2 先生已经出色的完成了工作;

这时 proposer-1 小姐找到了 2 号官员,官员告诉她合同已经签了,将合同给她看,proposer-1小姐是一个没有什么职业操守的聪明人,觉得跟 Client1 老板混没什么前途,所以将自己的议题修改为“Client2 老板中标”,并且给了 2 号官员 20 比特币,这样形成了一个多数派。

顺利的再次进入第二阶段。由于此时没有人竞争了,顺利的找 3 位官员签合同,3 位官员看到议题与上次一次的合同是一致的,所以最终接受了,形成了多数派,proposer-1 小姐跳槽到 Client2 老板的公司去了。

总结:Paxos 过程结束了,这样,一致性得到了保证,算法运行到最后所有的 proposer 都投“client2 中标”所有的 acceptor 都接受这个议题,也就是说在最初的第二阶段,议题是先入为主的,谁先占了先机,后面的 proposer 在第一阶段就会学习到这个议题而修改自己本身的议题,因为这样没职业操守,才能让一致性得到保证,这就是 paxos 算法的一个过程。原来paxos 算法里的角色都是这样的不靠谱,不过没关系,结果靠谱就可以了。该算法就是为了追求结果的一致性。

4.2.ZK 集群解析

4.2.1. Zookeeper 集群特点

前面一种研究的单节点,现在来研究下 zk 集群,首先来看下 zk 集群的特点。

 顺序一致性:客户端的更新顺序与它们被发送的顺序相一致。

 原子性:更新操作要么成功要么失败,没有第三种结果。

 单一视图:无论客户端连接到哪一个服务器,客户端将看到相同的 ZooKeeper 视图。

 可靠性:一旦一个更新操作被应用,那么在客户端再次更新它之前,它的值将不会改变。

 实时性:连接上一个服务端数据修改,所以其他的服务端都会实时的跟新,不算完全的实时,有一点延时的

 角色轮换避免单点故障:当 leader 出现问题的时候,会选举从 follower 中选举一个新的 leader

4.2.2. 集群中的角色

 Leader 集群工作机制中的核心:

1.事务请求的唯一调度者和处理者,保证集群事务处理的顺序性

2.集群内部服务器的调度者(管理 follower,数据同步)

 Follower 集群工作机制中的跟随者:

1.处理非事务请求,转发事务请求给 Leader

2.参与事务请求 proposal 投票

3.参与 leader 选举投票

 Observer 观察者:

1.3.30 以上版本提供,和 follower 功能相同,但不参与任何形式投票

2.处理非事务请求,转发事务请求给 Leader

3.提高集群非事务处理能力

4.2.4. Zookeeper 集群一致性协议 ZAB 解析

4.2.4.1. 总览

懂了 paxos 算法,其实 zab 就很好理解了。很多论文和资料都证明 zab 其实就是 paxos 的一种简化实现,但 Apache 自己的立场说 zab 不是 paxos 算法的实现,这个不需要去计较。

zab 协议解决的问题和 paxos 一样,是解决分布式系统的数据一致性问题;zookeeper 就是根据 zab 协议建立了主备模型完成集群的数据同步(保证数据的一致性),前面介绍了集群的各种角色,这说所说的主备架构模型指的是,在 zookeeper 集群中,只有一台 leader(主节点)负责处理外部客户端的事务请求(写操作),leader 节点负责将客户端的写操作数据同步到所有的 follower 节点中。

zab 协议核心是在整个 zookeeper 集群中只有一个节点即 leader 将所有客户端的写操作转化为事务(提议 proposal)。leader 节点再数据写完之后,将向所有的 follower 节点发送数据广播请求(数据复制),等所有的 follower 节点的反馈,在 zab 协议中,只要超过半数 follower节点反馈 ok,leader 节点会向所有 follower 服务器发送 commit 消息,既将 leader 节点上的数据同步到 follower 节点之上。

发现,整个流程其实和 paxos 协议其实大同小异。说 zab 是 paxos 的一种实现方式其实并不过分。

Zab 再细看可以分成两部分。第一的消息广播模式,第二是崩溃恢复模式。

正常情况下当客户端对 zk 有写的数据请求时,leader 节点会把数据同步到 follower 节点,这个过程其实就是消息的广播模式;

在新启动的时候,或者 leader 节点奔溃的时候会要选举新的 leader,选好新的 leader 之后会进行一次数据同步操作,整个过程就是奔溃恢复。

4.2.4.2. 消息广播模式

为了保证分区容错性,zookeeper 是要让每个节点副本必须是一致的。

1. 在 zookeeper 集群中数据副本的传递策略就是采用的广播模式;

2. Zab 协议中的 leader 等待 follower 的 ack 反馈,只要半数以上的 follower 成功反馈就好,不需要收到全部的 follower 反馈。

zookeeper 中消息广播的具体步骤如下:

1. 客户端发起一个写操作请求

2. Leader 服务器将客户端的 request 请求转化为事物 proposql 提案,同时为每个 proposal 分配一个全局唯一的 ID,即 ZXID

3. leader 服务器与每个 follower 之间都有一个队列,leader 将消息发送到该队列

4. follower 机器从队列中取出消息处理完(写入本地事物日志中)毕后,向 leader 服务器发送ACK 确认

5. leader 服务器收到半数以上的 follower 的 ACK 后,即认为可以发送 commit

6. leader 向所有的 follower 服务器发送 commit 消息

zookeeper 采用 ZAB 协议的核心就是只要有一台服务器提交了 proposal,就要确保所有的服务器最终都能正确提交 proposal。这也是 CAP/BASE 最终实现一致性的一个体现。

回顾一下:前面还讲了 2pc 协议,也就是两阶段提交,发现流程 2pc 和 zab 还是挺像的,zookeeper 中数据副本的同步方式与二阶段提交相似但是却又不同。二阶段提交的要求协调者必须等到所有的参与者全部反馈 ACK 确认消息后,再发送 commit 消息。要求所有的参与者要么全部成功要么全部失败。二阶段提交会产生严重阻塞问题,但 paxos 和 zab 没有这要求。

为了进一步防止阻塞,leader 服务器与每个 follower 之间都有一个单独的队列进行收发消息,使用队列消息可以做到异步解耦。leader 和 follower 之间只要往队列中发送了消息即可。如果使用同步方式容易引起阻塞。性能上要下降很多。

4.2.4.3. 崩溃恢复

4.2.4.3.1. 背景(什么情况下会崩溃恢复)

zookeeper 集群中为保证任何所有进程能够有序的顺序执行,只能是 leader 服务器接受写请求,即使是 follower 服务器接受到客户端的请求,也会转发到 leader 服务器进行处理。

如果 leader 服务器发生崩溃(重启是一种特殊的奔溃,这时候也没 leader),则 zab 协议要求zookeeper 集群进行崩溃恢复和 leader 服务器选举。

4.2.4.3.2. 最终目的(恢复成什么样)

ZAB 协议崩溃恢复要求满足如下 2 个要求:确保已经被 leader 提交的 proposal 必须最终被所有的 follower 服务器提交。确保丢弃已经被 leader 出的但是没有被提交的 proposal。

新选举出来的 leader 不能包含未提交的 proposal,即新选举的 leader 必须都是已经提交了的proposal 的 follower 服务器节点。同时,新选举的 leader 节点中含有最高的 ZXID。这样做的好处就是可以避免了 leader 服务器检查 proposal 的提交和丢弃工作。

 每个 Server 会发出一个投票,第一次都是投自己。投票信息:(myid,ZXID)

 收集来自各个服务器的投票

 处理投票并重新投票,处理逻辑:优先比较 ZXID,然后比较 myid

 统计投票,只要超过半数的机器接收到同样的投票信息,就可以确定 leader

 改变服务器状态

问题:为什么优先选大的 zxid?

答:因为ZXID比较大的代表该节点的服务器的数据完整性是比其他节点大的。

4.2.5. Java 客户端连接集群

ZK 连接集群很简单,只需要把连接地址用逗号分隔就好。

你可能感兴趣的:(ZooKeeper第四天)