分布式一致性协议 --1 ZAB协议

1.1 zookeeper提够了什么

特性解释
顺序一致从一个客户端发起对的,最终将会严格的按照顺序被应用到zookeeper中心去
原子性节点点保持原子性
单一视图无论客户端连接到哪个节点,看到的服务器,看到的服务器模型都是一致的
可靠性一旦完成了事物,这个状态就会被保存下来
实时性客户端能读到服务器端的最新数据,zookeeper只保证在一定时间内

1.2 zookeeper能做什么

1.2.1 数据发布/订阅

配置中心。zk采取拉/推结合模式。客户端向服务器注册自己关注的节点,一旦节点数据变更,服务器会向相应的客户端服务发送watcher时间通知。
1.定时检测配置文件变更,对配置进行更新
2.借助内存变量实现配置管理。可以采用JMX方式对系统运行时内存变量更新。
3.借助zk
前两种,在集群变大,配置信息多,无法满足。

1.2.2 负载权衡

zk的负载均衡是可以调控,nginx只是能调权重,其他需要可控的都需要自己写插件;但是nginx的吞吐量比zk大很多,应该说按业务选择用哪种方式

zookeepernginx
不存在单点问题,zab机制保证单点故障可重新选举一个leader存在单点问题,单点负载高数据量大
只负责服务的注册与发现,不负责转发,减少一次数据交换(消费方与服务方直接通信)每次负载,都充当一次中间人转发角色,增加网络负载量(消费方与服务方间接通信)
需要自己实现相应的负载均衡算法自带负载均衡算法

1.2.3 命名服务

集群的集群、提供服务的IP或远程对象。广义上的命名服务资源定位不是真正的实体资源,上层应用仅仅需要一个全局唯一的名字。
zk用于生成UUID,数据库自增id。

1.2.4 分布式协调/通知

xxxxxxxxxx

1.2.5 集群管理

集群管理分为集群监控与集群控制。前者侧重对集群运行时状态收集,后者侧重集群操作与控制。

1.2.6 Master选举

Master常常负责处理一些逻辑复杂的逻辑,并将处理结果同步给你集群中其他系统单元。只有一个客户端创建成功,吃点建节点成功,成了master。其他没有成功的机器注册该节点的watcher事件,其余Master会重新进行Master选举。

1.2.7 分布式锁

1.排他锁
定义锁:通过zk的数据节点表示一个锁,例如、/exclusive/lock
获取锁:所有客户端用create创建临时节点,只用一个能穿件成功,其他客户端监听该节点变更的监听。
释放锁:节点宕机;客户端删除节点。
2.共享锁
定义锁:在zk节点创建/shared_lock/请求类型(R读或W写)+序号。
获取锁:创建临时顺序节点。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

1.2.8 分布式队列

1.FIFO队列
所有客户端都到某个节点如/queue,创建顺序临时节点。
1、通过getchildren接口,获取全部元素
2.确定自己节点顺序
3.如果不是最小的,进入等待,同时进入等待,同时向比自己小的最后一个节点注册watcher,
4.收到watcher重复步骤1.
2.Barrier
比起FIFO,先创建一个节点如/queue默认值,如10.
1.读取节点/queue默认值
2、通过getchildren接口,获取全部元素,注册子节点变更watcher
3.统计子节点个数
4.子节点个数不满10个,进入等待
5.重复步骤2.

1.3 基本概念

1.3.1 集群角色

1.leader
leader服务器为客户端提够读和写服务
2.follower
提够读服务
3.observe
提够读服务,但不参与leader选举和操作“过半写成功”,Observer介意在不影响写的情况下,提升集群的读性能。

1.3.2 会话

指的是客户端回话,·客户端启动回服务器建立一个TCP连接,默认端口号2181,sessionTimeout标志客户端会话超时时间,只要在sessionTimeout时间内,连接集群的额任意节点,当前session就有效。

1.3.3 数据节点

  1. PERSISTENT-持久节点
    除非手动删除,否则节点一直存在于Zookeeper上

  2. EPHEMERAL-临时节点
    临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与zookeeper连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。

  3. PERSISTENT_SEQUENTIAL-持久顺序节点
    基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。

  4. EPHEMERAL_SEQUENTIAL-临时顺序节点
    基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。

1.3.4 版本

zookeeper会为维护一个叫Stat的数据结构,Stat包含version(当前节点版本)、cversion(当前子节点版本)、aversion(当前Znode的ACL版本)

1.3.5 watcher

在指定节点支持特性事件,zk服务端将事物通知发送到感兴趣的客户端上。

1.3.6 ACL

zk定义了5种权限:

  1. CREATE 只针对子节点
  2. READ
  3. WRITE
  4. DELETE 只针对子节点
  5. ADMIN

2. ZAB协议

zk实现了一种主备模式的系统架构架构来保持洁群中各个副本之间数据一致性。zk使用单一的主进程接收并处理客户端的所有请求。ZAB协议包括两种基本模式:崩溃恢复和消息广播。

2.1 崩溃恢复和消息广播如何切换

  1. 从崩溃恢复到消息广播
    当集群的有半数以上follower与leader 保持一致。
  2. 从消息广播切换到崩溃恢复
    当leader服务器崩溃退出或机器重启,或者不存在超过一半以上的服务器与leader正常通讯
  3. 新加入的符合ZAB协议的服务器
    如果集群有个leader服务器,那么该节点自觉进入数据恢复模式。

2.2 消息广播

  1. leader服务器会生成对应的是事物Proposal,并为这个proposal分配一个单调递增的唯一ID(ZXID)。将发送集群中的所有机器,分别收集所有选票。follower服务器只能接受或抛弃proposal。ZAB协议在第二段提交过程中,只要超过半数Follower的应答就可以提交Proposal了。整个广播基于TCP协议,能够保证消息接收和发送的顺序。
  2. leader会为每个follower服务器分配一个单独的队列,

xxxxxxxxxxxxxxxxxxxxxxx

2.3 崩溃恢复

1.ZAB协议必须保证那些已经在leader上提交的事物,最终被所有的服务器都提交
2.ZAB协议必须确保丢弃那些只在leader服务器上提出的事物。
leader选取拥有最大的ZXID编号。

2.4 数据同步

  1. 新的leade选举后,leader会确认事物日志中的proposal都已被半数以上follower的提交了,即完成了同步。leader服务器会为未完成同步的follower服务器建立一个单独的队列,发送proposal,紧接着发送commit消息。
  2. ZXID高32 位表示leader周期,每次新的leader产生,就冲事物的日志取出最大的ZXID,并加1.

2.5 深入ZAB协议

3 ZAB 与paxos算法联系

3.1 相同点

1.两者leader和follower角色运行
2.leader等到半数以上的follower反馈后,才会将下一个提案进行
3.ZAB 用ZXID表示leader周期,在paxos变为Ballot

3.2 不同点

1.除了读写,ZAB多了一个同步阶段
2.ZAB的设计目标构建一个高可用的分布式数据主备系统,而paxos算法用于构建一个分布式一致性的状态机系统。

4 zk技术内幕

4.1 节点特性

4.1.1节点的状态信息stat结构

使用get命令可以查询节点状态,除了第一行,其他都是状态。

状态属性说明
czxid即Create ZXID,表示该数据接饿到被创建时的事物ID
mzxid即Modified ZXID,表示最后一次修改更新的事物ID
ctime即Modifed Time,节点被创建的时间
mtime即Modified Time,表示节点最后一次被更新的时间
version数据节点的版本号
cversion子节点版本号
aversion节点的ACL版本号
ephemeralOwner创建该临时节点的会话的sessionID。如果该节点是持久节点,那么这个属性为0
dataLength数据内容长度
numChildren当前节点的子节点个数
pzxid子节点最后一次修改的事物Id,只有子节点的列表变化才会更新pzxid,子节点内容变更不会影响pzxid

6 Leader选举过程

  1. 初始化Leader选举。集群模式特有,Zookeeper首先会根据自身的服务器ID(SID)、最新的ZXID(lastLoggedZxid)和当前的服务器epoch(currentEpoch)来生成一个初始化投票,在初始化过程中,每个服务器都会给自己投票。然后,根据zoo.cfg的配置,创建相应Leader选举算法实现,Zookeeper提供了三种默认算法(LeaderElection、AuthFastLeaderElection、FastLeaderElection),可通过zoo.cfg中的electionAlg属性来指定,但现只支持FastLeaderElection选举算法在初始化阶段,Zookeeper会创建Leader选举所需的网络I/O层QuorumCnxManager,同时启动对Leader选举端口的监听,等待集群中其他服务器创建连接。

  2. 注册JMX服务。

  3. 检测当前服务器状态。运行期间,QuorumPeer会不断检测当前服务器状态。在正常情况下,Zookeeper服务器的状态在LOOKING、LEADING、FOLLOWING/OBSERVING之间进行切换。在启动阶段,QuorumPeer的初始状态是LOOKING,因此开始进行Leader选举。

  4. Leader选举。通过投票确定Leader,其余机器称为Follower和Observer。具体算法在后面会给出。

6.1 服务启动时的选举

1、每个server会发出一个投票,由于是初始情况,因此对于server1和server2来说,都会将自己作为leader服务器来投票,每次投票包含的最基本的元素为:所推举的服务器的myid和zxid,我们以(myid, zxid)的形式来表示。因为是初始化阶段,因此无论是server1和是server2都会投给自己,即server1的投票为(1, 0),server2的投票为(2, 0),然后各自将这个投票发给集群中其它所有机器。

2、接收来自各个服务器的投票
每个服务器都会接收来自其它服务器的投票,接收到后会判断该投票的有效性,包括检查是否是本轮投票,是否来自looking状态的服务器

3、处理投票
在接收到来自其它服务器的投票后,针对每一个投票,服务器都需要将别人的投票和自己的投票进行pk, pk的规则如下:
(1)优先检查zxid,zxid大的服务器优先作为leader。
(2)如果zxid相同,那么比较myid,myid大的服务器作为leader服务器。

现在我们来看server1和server2实际是如何进行投票的,对于server1来说,他自己的投票是(1, 0),而接收到的投票为(2, 0)。首先会对比两者的zxid,因为都是0,所以接下来会比较两者的myid,server1发现接收到的投票中的myid是2,大于自己,于是就会更新自己的投票为(2, 0),然后重新将投票发出去,而对于server2,不需要更新自己的投票信息,只是再一次向集群中的所有机器发出上一次投票信息即可。
4、统计投票
每次投票后,服务器都会统计所有投票,判断是否已经有过半的机器接收到相同的投票信息,对于server1和server2来说,都统计出集群中已经有两台机器接受了(2, 0)这个投票信息。这里过半的概念是指大于集群机器数量的一半,即大于或等于(n/2+1)。对于这里由3台机器构成的集群。大于等于2台即为达到过半要求。

5、改变服务器状态
一旦确定了leader,每个服务器就会更新自己的状态,如果是follower,那么就变更为following,如果是leader,就变更为leading

6.2 运行时的选举

1、变更状态
当leader挂了之后,余下的非observer服务器都会将自己的状态变为looking,然后开始进行leader选举流程。

2、每个server会发出一个投票
在这个过程中,需要生成投票信息(myid, zxid),因为是运行期间,因此每个服务器上的zxid可能不同,我们假定server1的zxid为123,而server3的zxid为122.在第一轮投票中,server1和server3都会投给自己,即分别产生投票(1, 123)和(3, 122),然后各自将这个投票发给集群中的所有机器。

3、接收来自各个服务器的投票

4、处理投票
对于投票的处理,和上面提到的服务器启动期间的处理规则是一致的,在这个例子中,由于server1的zxid是123,server3的zxid是122,显然server1会成为leader。

5、统计投票

6、改变服务器状态

6.3 Leader选举的算法分析

6.3.1术语解释

SID: 在zoo.cfg文件中,对集群中的每一个server都赋予一个id,标识着集群中的一台server。每台机器不能重复,和myid值一致

epoch:代表一个Leader周期。每当一个新的leader产生,该leader便会从服务器本地日志中最大事务Proposal的zxid解析出epoch值,然后对其进行+1操作,作为新的epoch.

zxid:事务ID,标识这对一次服务器状态的变更。是一个64bit的long数值,高32位标识着当前epoch,低32位是计数器。Leader在产生一个新的事务Proposal的时候,都会对该计数器进行+1操作。
新的Leader产生的时候,epoch+1的同时,低32会被置为0,在此基础上开始生成新的ZXID

Vote:投票

Quorum:过半机器数

6.3.2 Leader选举的算法分析

当zookeeper集群中的一台服务器出现以下两种情况之一时,就会开始进入leader选举:

1、服务器初始化启动

2、服务器运行期间无法和leader保持连接

而当一台机器进入leader选举流程时,当前集群也可能会处于以下两种状态:

1、集群中本来就已经存在一个leader

2、集群中确实不存在leader

第一种已经存在leader的情况,这种情况通常是集群中的某一台机器启动比较晚,在它启动之前,集群已经可以正常工作,即已经存在一台leader服务器。针对这种情况,当该机器试图去选举leader的时候,会被告知当前服务器的leader信息,对于该机器来说,仅仅需要和leader机器建立连接,并进行状态同步即可。

小结:那台服务器数据越新,ZXID越大,当然几个服务器的ZXID相同,SID越大越有可能选为leader。

6.4 Leader选举的实现细节

6.4.1 服务器状态

1.LOOKING:寻找Leader状态。处于该状态的服务器会认为当前集群中不存在Leader,然后发起leader选举。

2.FOLLOWING:表明当前服务器角色是Follwer

3.LEADING:表明当前服务器角色是Leader

4.OBSERVING:表明当前服务器角色是Observer,不参与Leader选举

6.4.2 选票结构

属性说明
id被选举leaderSID值
zx id被选举leaderde 事物id
electionEpoch逻辑时钟,用来判断多个投票是否在同一个周期总,每次自增
peerEpoch被推举的leader的epoch
state当前服务器的状态

6.4.3FastLeaderElection选举算法

基本概念:
1 外部投票
2 内部投票
3 选举轮次 leader的轮次
4 PK 指内部投票与外部投票进行以确定是否 需要变更内部投票。

算法核心:
在这里插入图片描述

1 自增选举次数
logicalclock标识选举的次数。
2 初始化选票

属性说明
id当前服务器的自身的SID
zx id当前服务器的最新的 ZXID
electionEpoch当前服务器的选举轮次
peerEpoch被推举的leader的选举轮次
stateLOOKING

3 发送初始化选票
zk将初始化的选票放入sendqueue队列,由发送workersender发送出去。
4 接收外部投票
每台服务器会不断的从recqueue中能拉外部投票。如果没有选票,立即确认自己是否与集群连接正确。,没有则进行连接,发送选票。
5 判断选举轮次
(1)外部选票轮次大于内部选票时,更新自身的logicalclock,清空选票,使用初始化的投票进行PK已确定是否 变更内部投票,最终将内部投票发送出去。
(2)外部投票小于内部投票, zk直接忽略外部处理,返回第四部。
(3)内外部选票一致,进行下一步选票PK.
6 选票PK
判断当前选票是否需要变更,从选举轮次、ZXID和SID考虑:满足任意条件就变更
(1)外部投票中被推举的leader服务器的选举轮次大于内部选票
(2)轮次一致,对比ZXID外部投票的ZXID大于内部ZXID
(3)SID外部大于内部
7选举变更
外部优于内部选票,外部选票覆盖内部选票,再将这个内部投票发送出去。
8 选票归档
将刚收到外面的投票,放入选票集合。recvset集合中。
9 统计选票
统计选票 是否过半
10 更新服务器状态
等待200ms,看是否有更优的解,服务器率先确定自己leader,更新为leading,

7 羊群效应

8 Zookeeper3.4.6版本是否存在脑裂问题

首先,Zookeeper3.4.6不存在脑裂的问题

Zookeeper3.4.6的选举算法是FastLeaderElection,该算法的规则是投票超过半数的服务器才能当选为Leader。这个算法能够保证leader唯一。

解决脑裂一般有3种方式:

1 Quorums(ˈkwôrəm 法定人数) ,比如3个节点的集群,Quorums = 2, 也就是说集群可以容忍1个节点失效,这时候还能选举出1个lead,集群还可用。比如4个节点的集群,它的Quorums = 3,Quorums要超过3,相当于集群的容忍度还是1,如果2个节点失效,那么整个集群还是无效的。
2 采用Redundant communications,冗余通信的方式,集群中采用多种通信方式,防止一种通信方式失效导致集群中的节点无法通信。
3 Fencing, 共享资源的方式,比如能看到共享资源就表示在集群中,能够获得共享资源的锁的就是Leader,看不到共享资源的,就不在集群中。

ZooKeeper默认采用了Quorums这种方式,即只有集群中超过半数节点投票才能选举出Leader。这样的方式可以确保leader的唯一性,要么选出唯一的一个leader,要么选举失败。

8 实现分布式锁

8. 1 排它锁又称为写锁或独占锁

在这里插入图片描述

8. 1 .1 获取锁

1 非公平锁
在需要获取排它锁时,所有客户端通过调用接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。Zookeeper可以保证只有一个客户端能够创建成功,没有成功的客户端需要注册/exclusive_lock节点监听。
非公平锁,会有羊群效应。

2 公平锁
基本流程

  1. 客户端连接上zookeeper,并在指定节点(locks)下创建临时顺序节点node_n

  2. 客户端获取locks目录下所有children节点

  3. 客户端对子节点按节点自增序号从小到大排序,并判断自己创建的节点是不是序号最小的,若是则获取锁;若不是,则监听比该节点小的那个节点的删除事件

  4. 获得子节点变更通知后重复此步骤直至获得锁;

  5. 执行业务代码,完成业务流程后,释放锁,删除对应的子节点释放锁

实现两种方式:

1原生方式,使用java和zk api书写以上逻辑实现分布式锁,操作zookeeper使用的是apache提供的zookeeper的包。通过实现Watch接口,实现process(WatchedEvent event)方法来实施监控,使CountDownLatch来完成监控,在等待锁的时候使用CountDownLatch来计数,等到后进行countDown,停止等待,继续运行。

2 Curator框架来实现分布式锁,Curator是Netflix公司一个开源的zookeeper客户端,在原生API接口上进行了包装,解决了很多ZooKeeper客户端非常底层的细节开发。同时内部实现了诸如Session超时重连,Watcher反复注册等功能,实现了Fluent风格的API接口。

参考
https://blog.csdn.net/u010028869/article/details/84034261
ZooKeeper框架Curator分布式锁实现及源代码分析 https://blog.csdn.net/liyiming2017/article/details/83896169
https://blog.csdn.net/liyiming2017/article/details/83896169
https://blog.csdn.net/u010285974/article/details/80556480

8. 1 .2 释放锁

当获取锁的客户端宕机或者正常完成业务逻辑都会导致临时节点的删除,此时,所有在/exclusive_lock节点上注册监听的客户端都会收到通知,可以重新发起分布式锁获取。

8. 2 共享锁

① 获取锁,在需要获取共享锁时,所有客户端都会到/shared_lock下面创建一个临时顺序节点,如果是读请求,那么就创建例如/shared_lock/host1-R-00000001的节点,如果是写请求,那么就创建例如/shared_lock/host2-W-00000002的节点。

② 判断读写顺序,不同事务可以同时对一个数据对象进行读写操作,而更新操作必须在当前没有任何事务进行读写情况下进行,通过Zookeeper来确定分布式读写顺序,大致分为四步。

  1. 创建完节点后,获取/shared_lock节点下所有子节点,并对该节点变更注册监听。

  2. 确定自己的节点序号在所有子节点中的顺序。

  3. 对于读请求:若没有比自己序号小的子节点或所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到共享锁,同时开始执行读取逻辑,若有写请求,则需要等待。对于写请求:若自己不是序号最小的子节点,那么需要等待。

  4. 接收到Watcher通知后,重复步骤1。

③ 释放锁,其释放锁的流程与独占锁一致。

上述共享锁的实现方案,可以满足一般分布式集群竞争锁的需求,但是如果机器规模扩大会出现一些问题,下面着重分析判断读写顺序的步骤3。

针对如上图所示的情况进行分析

  1. host1首先进行读操作,完成后将节点/shared_lock/host1-R-00000001删除。

  2. 余下4台机器均收到这个节点移除的通知,然后重新从/shared_lock节点上获取一份新的子节点列表。

  3. 每台机器判断自己的读写顺序,其中host2检测到自己序号最小,于是进行写操作,余下的机器则继续等待。

  4. 继续…

可以看到,host1客户端在移除自己的共享锁后,Zookeeper发送了子节点更变Watcher通知给所有机器,然而除了给host2产生影响外,对其他机器没有任何作用。大量的Watcher通知和子节点列表获取两个操作会重复运行,这样会造成系能鞥影响和网络开销,更为严重的是,如果同一时间有多个节点对应的客户端完成事务或事务中断引起节点小时,Zookeeper服务器就会在短时间内向其他所有客户端发送大量的事件通知,这就是所谓的羊群效应。

可以有如下改动来避免羊群效应。

  1. 客户端调用create接口常见类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点。

  2. 客户端调用getChildren接口获取所有已经创建的子节点列表(不注册任何Watcher)。

  3. 如果无法获取共享锁,就调用exist接口来对比自己小的节点注册Watcher。对于读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。对于写请求:向比自己序号小的最后一个节点注册Watcher监听。

  4. 等待Watcher通知,继续进入步骤2。

此方案改动主要在于:每个锁竞争者,只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可。

9 实现Master选举

参考

1 https://blog.csdn.net/paincupid/article/details/78058087
2 从Paxos到zookeeper分布式一致性原理与实践
3 https://www.jianshu.com/p/2bceacd60b8a
4 https://www.cnblogs.com/frankltf/p/10392151.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值