zookeeper一文精通(下)

zookeeper一文精通(下)

本文主要带着大家来学习一下zk里面的高级部分-Paxos算法算法、zab协议和leader选举机制。

想查看更多的文章请关注公众号:IT巡游屋
在这里插入图片描述

Paxos算法
什么是Paxos协议

​ Paxos算法是一种基于消息传递且具有高度容错特性的一致性算法。

​ Google的粗粒度锁服务Chubby的设计开发者Burrows曾经说过:“所有一致性协议本质上要么是Paxos要么是其变体”,并且在过去十年里,Paxos基本成为了分布式领域内一致性协议的代名词。Paxos的提出者Lamport也因其对分布式系统的杰出理论贡献获得了2013年图灵奖。

Paxos解决了什么问题

​ 在常见的分布式系统中,总会发生诸如机器宕机或网络异常等情况,Paxos算法需要解决的问题就是如何在分布式环境中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。

Paxos的角色
  1. 提议者(proposer):进行提议的角色
  2. 批准者(acceptor):通过提议的角色
  3. 学习者learner):感知(learn)被选定的提议

在具体的实现中,一个进程可能同时充当多种角色。比如一个进程可能既是Proposer又是Acceptor又是Learner

Paxos的执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bQAkBK0G-1601007781857)(assets/05-Paxos.png)]

看这个图就知道这个算法还是有一定难度的,他将整个过程分为了2个阶段来进行,那么我们举列来说明每个阶段所作的事情:

第一阶段:

提议者对接收者吼了一嗓子,我有个事情要告诉你们,当然这里接受者不只一个,它也是个分布式集群

相当于星期一开早会,领导吼了句:“要开会了啊,我要公布一个编号为001的提案,收到请回复”。

这个时候领导就会等着,等员工回复1“好的”,如果回复的数目超过一半,就会进行下一步。

如果由于某些原因(接收者死机,网络问题,本身业务问题),导通过的协议未超过一半,

这个时候的领导又会再吼一嗓子,当然气势没那凶残:“好了,怕了你们了,我要公布一个新的编号未002的提案,收到请回复1”。

第二阶段:

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

问题:

通过刚刚的例子大家应该对Paxos协议有一定的了解了,但是以上的例子会有2个问题存在:

  1. 单点问题: 如果员工都不听话,那么领导肯定没有办法干下去,领导一走那么提议者就没有了,所以提议者存在dandian
  2. 一致性问题: 如果员工抬扛无论提议者提议什么都采取拒绝的方案,那么提议永远不可能达成一致

所以Paxos协议肯定不会只有一个提议者,作为下属的员工也不能全是杠精,所以协议规定:

  1. 如果接收者没有收到过提案编号,他必须接受第一个提案编号
  2. 如果接收者没有收到过其他协议,他必须接受第一个协议

ZAB 协议

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-heWFdprq-1601007781860)(assets/10-1.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pzrrCUal-1601007781864)(assets/10-2.png)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZoRwGzbD-1601007781866)(assets/10-3.png)]

正常情况下当客户端对zk有写的数据请求时,leader节点会把数据同步到follower节点,这个过程其实就是消息的广播模式在新启动的时候,或者leader节点奔溃的时候会要选举新的leader,选好新的leader之后会进行一次数据同步操作,整个过程就是奔溃恢复。

消息广播模式

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

  1. 在zookeeper集群中数据副本的传递策略就是采用的广播模式
  2. Zab协议中的leader等待follower的ack反馈,只要半数以上的follower成功反馈就好,不需要收到全部的follower反馈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TpPkQy2u-1601007781869)(assets/10-4.png)]

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最终实现一致性的一个体现。

zookeeper中数据副本的同步方式与二阶段提交相似但是却又不同。二阶段提交的要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功要么全部失败。二阶段提交会产生严重阻塞问题,但paxos和2pc没有这要求。

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

崩溃恢复

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7BHQPL7R-1601007781870)(assets/10-5.png)]

出现的场景:

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

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

恢复过程:

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

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

  • 每个Server会发出一个投票,第一次都是投自己。投票信息:(myid,ZXID)
  • 收集来自各个服务器的投票
  • 处理投票并重新投票,处理逻辑:优先比较ZXID,然后比较myid
  • 统计投票,只要超过半数的机器接收到同样的投票信息,就可以确定leader
  • 更新服务器状态

领导选举场景
1. 集群启动领导选举

初始投票给自己
集群刚启动时,所有服务器的logicClock都为1,zxid都为0。

各服务器初始化后,都投票给自己,并将自己的一票存入自己的票箱,如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ppYeDg7h-1601007781872)(assets/image-20200909155613353.png)]

在上图中,(1, 1, 0)第一位数代表投出该选票的服务器的logicClock,第二位数代表被推荐的服务器的myid,第三位代表被推荐的服务器的最大的zxid。由于该步骤中所有选票都投给自己,所以第二位的myid即是自己的myid,第三位的zxid即是自己的zxid。

此时各自的票箱中只有自己投给自己的一票。

更新选票
服务器收到外部投票后,进行选票PK,相应更新自己的选票并广播出去,并将合适的选票存入自己的票箱,如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FIW2S0q9-1601007781873)(assets/image-20200909155633859.png)]

服务器1收到服务器2的选票(1, 2, 0)和服务器3的选票(1, 3, 0)后,由于所有的logicClock都相等,所有的zxid都相等,因此根据myid判断应该将自己的选票按照服务器3的选票更新为(1, 3, 0),并将自己的票箱全部清空,再将服务器3的选票与自己的选票存入自己的票箱,接着将自己更新后的选票广播出去。此时服务器1票箱内的选票为(1, 3),(3, 3)。

同理,服务器2收到服务器3的选票后也将自己的选票更新为(1, 3, 0)并存入票箱然后广播。此时服务器2票箱内的选票为(2, 3),(3, ,3)。

服务器3根据上述规则,无须更新选票,自身的票箱内选票仍为(3, 3)。

服务器1与服务器2更新后的选票广播出去后,由于三个服务器最新选票都相同,最后三者的票箱内都包含三张投给服务器3的选票。

根据选票确定角色
根据上述选票,三个服务器一致认为此时服务器3应该是Leader。因此服务器1和2都进入FOLLOWING状态,而服务器3进入LEADING状态。之后Leader发起并维护与Follower间的心跳。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O0EfITFA-1601007781875)(assets/image-20200909155654216.png)]

2. Follower重启

Follower重启,或者发生网络分区后找不到Leader,会进入LOOKING状态并发起新的一轮投票。首先Follower重启投票给自己

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XeM0soGD-1601007781878)(assets/image-20200909155723322.png)]

发现已有Leader后成为Follower,服务器3收到服务器1的投票后,将自己的状态LEADING以及选票返回给服务器1。服务器2收到服务器1的投票后,将自己的状态FOLLOWING及选票返回给服务器1。此时服务器1知道服务器3是Leader,并且通过服务器2与服务器3的选票可以确定服务器3确实得到了超过半数的选票。因此服务器1进入FOLLOWING状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BPFaWET9-1601007781879)(assets/image-20200909155740571.png)]

3. Leader重启

Leader(服务器3)宕机后,Follower(服务器1和2)发现Leader不工作了,因此进入LOOKING状态并发起新的一轮投票,并且都将票投给自己。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pD1Iv0rO-1601007781881)(assets/image-20200909155759829.png)]

使用ZAB协议广播更新选票

服务器1和2根据外部投票确定是否要更新自身的选票。这里有两种情况

1. 服务器1和2的zxid相同。例如在服务器3宕机前服务器1与2完全与之同步。此时选票的更新主要取决于myid的大小
2. 服务器1和2的zxid不同。在旧Leader宕机之前,其所主导的写操作,只需过半服务器确认即可,而不需所有服务器确认。换句话说,服务器1和2可能一个与旧Leader同步(即zxid与之相同)另一个不同步(即zxid比之小)。此时选票的更新主要取决于谁的zxid较大

在上图中,服务器1的zxid为11,而服务器2的zxid为10,因此服务器2将自身选票更新为(3, 1, 11)。

经过上一步选票更新后,服务器1与服务器2均将选票投给服务器1,因此服务器2成为Follower,而服务器1成为新的Leader并维护与服务器2的心跳

在这里插入图片描述

旧Leader恢复后发起选举

旧的Leader恢复后,进入LOOKING状态并发起新一轮领导选举,并将选票投给自己。此时服务器1会将自己的LEADING状态及选票(3, 1, 11)返回给服务器3,而服务器2将自己的FOLLOWING状态及选票(3, 1, 11)返回给服务器3。如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hs7mL1Nx-1601007781883)(assets/image-20200909155905948.png)]

旧Leader成为Follower

服务器3了解到Leader为服务器1,且根据选票了解到服务器1确实得到过半服务器的选票,因此自己进入FOLLOWING状态。

1, 11)返回给服务器3,而服务器2将自己的FOLLOWING状态及选票(3, 1, 11)返回给服务器3。如下图所示。

[外链图片转存中...(img-Hs7mL1Nx-1601007781883)]

旧Leader成为Follower

服务器3了解到Leader为服务器1,且根据选票了解到服务器1确实得到过半服务器的选票,因此自己进入FOLLOWING状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3vyC962L-1601007781884)(assets/image-20200909155924680.png)]

什么是ZooKeeperZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。 ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。 Rabbit ZooKeeper Extensions 该项目使用了 Apache ZooKeeper .NET async Client 组件,除提供了基本的zk操作,还额外封装了常用的功能以便让.net开发者更好的使用zookeeper。 提供的功能 session过期重连 永久watcher 递归删除节点 递归创建节点 跨平台(支持.net core) 使用说明 创建连接 IZookeeperClient client = new ZookeeperClient(new ZookeeperClientOptions         {             ConnectionString = "172.18.20.132:2181",             BasePath = "/", //default value             ConnectionTimeout = TimeSpan.FromSeconds(10), //default value             SessionTimeout = TimeSpan.FromSeconds(20), //default value             OperatingTimeout = TimeSpan.FromSeconds(60), //default value             ReadOnly = false, //default value             SessionId = 0, //default value             SessionPasswd = null //default value         }); 创建节点 var data = Encoding.UTF8.GetBytes("2016"); //快速创建临时节点 await client.CreateEphemeralAsync("/year", data); await client.CreateEphemeralAsync("/year", data, ZooDefs.Ids.OPEN_ACL_UNSAFE); //快速创建永久节点 await client.CreatePersistentAsync("/year", data); await client.CreatePersistentAsync("/year", data, ZooDefs.Ids.OPEN_ACL_UNSAFE); //完整调用 await client.CreateAsync("/year", data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); //递归创建 await client.CreateRecursiveAsync("/microsoft/netcore/aspnet", data, CreateMode.PERSISTENT); 获取节点数据 IEnumerable data = await client.GetDataAsync("/year"); Encoding.UTF8.GetString(data.ToArray()); 获取子节点 IEnumerable children= await client.GetChildrenAsync("/microsoft"); 判断节点是否存在 bool exists = await client.ExistsAsync("/year"); 删除节点 await client.DeleteAsync("/year"); //递归删除 bool success = await client.DeleteRecursiveAsync("/microsoft"); 更新数据 Stat stat = await client.SetDataAsync("/year", Encoding.UTF8.GetBytes("2017")); 订阅数据变化 await client.Subscrib
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值