Zookeeper学习笔记

注:Zookeeper = 文件系统 + 监听通知机制观察者机制

一、Zookeeper节点类型

PERSISTENT:持久化目录节点(客户端与zk断开后,该节点依旧存在)

PERSISTENT_SEQUENTIAL:持久化顺序目录节点(同上,但是zk给节点名称进行顺序编号)

EPHEMERAL:临时目录节点(客户端与zk断开后,该节点被删除)

EPHEMERAL_SEQUENTIAL:临时顺序目录节点(同上,但是zk给节点名称进行顺序编号)

二、投票选举

注:zxid的产生(每当跟随者发生一次写请求,都会发送一个zxid给领导者,zxid是唯一的不重复的)

选举流程:投给自己–>接收其他服务器的选票–>pk–>投票–>统计

zk集群选举最终原理是zab原子广播。

zab(恢复模式、广播模式(同步)):

选举算法:先比较键号,如果一样比较zxid(写操作序号),如果一样比较serverid(myid)

一致性问题:

强一致性:第2步读取时要么让同步操作耗时很短,要么将读取阻塞等待同步完成后在读取数据。(针对于集群)

弱一致性:读取数据允许为脏读

最终一致性:允许短暂读取的数据不一致,但最终读取的数据一定要一致。(需要一些手段实现数据库的同步、人工补偿)

在这里插入图片描述

2PC:

①领导---->选举机制(开启投票,投给自己,交流,比较更适合的人选,投票箱(recvset的一个hashmap),统计)

对于zk来说,比较哪个数据更加新。

选举时间点:

集群启动、领导(Leader挂掉)、Follower挂掉后Leader发现没有过半的Follower跟随自己不对外提供服务。

②过半机制

zk选举时有两个关键字段:zxid(写操作序号,zk日志中最大的处理数id,越大说明数据越新)、myid(zk配置文件定义好每个集群服务器的id)。投票时将两个字段的数放在投票箱进行投票。

在这里插入图片描述

同步:

③2pc(两段提交):

第一段:leader预提交给follower(持久化日志,将zxid发送给跟随者),follower收到通知后返回ack到leader(返回的跟随者数量超过半数,领导者才会把数据同步给各个跟随者),leader接收到之后发送commit(提交持久化日志)

在这里插入图片描述

三、分布式锁(非公平锁、互斥性)

实现原理

使用lock和unlock,先trylock尝试加锁(就是创建一个节点,创建成功则代表加锁成功,创建失败则代表加锁失败说明节点已存在,失败情况则进入阻塞状态cdl.await()方法进行阻塞,通过监听监控这个节点什么时候删除,然后在进行加锁),解锁就是删除这个节点。

缺点:同一时间只有一个请求能获取锁,其他的请求等待。当释放锁时所有请求去抢占,非常消耗资源。(产生惊群/羊群效应)

注:惊群效应,每次冲突或加锁失败都会通知所有的客户端,可能最后一个客户端还没有占有过锁,就被通知了n次。带来的问题如:①服务器的性能损耗②网络冲击③严重可能造成宕机

解决办法:用临时顺序节点方法加锁。

①请求进来,直接在/lock节点(容器)下创建一个临时顺序节点。

②判断自己是不是lock节点下最小节点(是则直接获取锁,不是则对前面节点进行监听watch)

③获得锁请求,处理完释放锁(delete节点),然后后继第一个节点接到通知重复②操作。

临时顺序节点加锁原理图:

在这里插入图片描述
在这里插入图片描述

1.线程进程同步的方法和机制

临界区:对多线程的串行化来访问公共资源或代码(sychronized修饰的java方法、仅用于线程同步)

互斥量:采用互斥对象机制,只有拥有互斥量对象才有访问资源的权限(sychronized修饰的代码,juc下的Lock,分布式锁主要实现机制)

信号量:允许同一时间多个线程访问一个资源,但需要在同一时刻访问资源的最大线程数目。(CountDownLacth,CylicBarrier,Semaphore)

事件:通过通知操作来保持任务的同步,还可以方便实现对多个任务优先级比较的操作

四、zkServer

1.加载配置文件

2.监听2181

3.领导者选举

4.加载数据内存

源码分析:

1)读取配置文件解析内容

2)启动时删除快照(可以根据设置好的时间删除,设置好留几个快照剩下的删除,默认是启动时删除)

3)如果配置文件有类似:server.1=localhost:2887:2887多个服务端口配置,则启动时按照集群启动,否则是单机启动。

4)集群启动时需要等待选举成功后NIO线程才能启动成功。

5)创建一个NIO线程,等待客户端发链接过来

在这里插入图片描述
投票图解:

在这里插入图片描述

Linux环境下使用ZK:

创建节点:create /zkPro myData

获取节点:get /zkPro

修改节点:get /zkPro myData123

删除节点:delete /zkPro

补充:

1.zookeeper在linux安装:

①wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.5.9/apache-zookeeper-3.5.9-bin.tar.gz

②tar -zxvf,并将conf中的cfg文件改名成zoo.cfg

③bin/zkServer.sh start启动zk服务(bin/zkCli.sh查看启动日志)

2.java api操作zk

简单的client客户端调用类

public class ZookeeperProSync implements Watcher { private static CountDownLatch connectedSemaphore = new CountDownLatch(1); private static ZooKeeper zk = null; private static Stat stat = new Stat(); public static void main(String[] args) throws Exception { //zookeeper配置数据存放路径 String path = “/username”; zk = new ZooKeeper(“192.168.64.129:2181”, 5000, new ZookeeperProSync()); //等待zk连接成功的通知 connectedSemaphore.await(); //获取path目录节点的配置数据,并注册到默认的监听器 System.out.println(new String(zk.getData(path, true, stat))); Thread.sleep(Integer.MAX_VALUE); } @Override public void process(WatchedEvent event) { if (Event.KeeperState.SyncConnected == event.getState()) {// zk连接成功通知事件 if (Event.EventType.None == event.getType() && null == event.getPath()) { connectedSemaphore.countDown(); } else if (event.getType() == Event.EventType.NodeDataChanged) {// zk目录节点数据变化通知事件 try { System.out.println(“配置已修改,新值为:” + new String(zk.getData(event.getPath(), true, stat))); } catch (Exception e) { } } } } }

1.Zookeeper是什么?

zookeeper是一个开源的分布式协调服务,集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。

Zookeeper保证了如下分布式一致性特性:

  • 顺序一致性
  • 原子性
  • 单一视图
  • 可靠性
  • 实时性(最终一致性)

2.zookeeper提供最重要的两个服务

文件系统+监听通知机制(观察者模式)

3.zookeeper文件系统

zk提供一个多层级的节点命令空间(znode)这些节点都可以设置关联数据,为了保证高吞吐低延迟,在内存中维护了树状的目录结构,所以zk每个节点存储上限为1MB。

4.ZAB协议

zab是zk专门设计的支持崩溃恢复的原子广播协议,包括两种基本的模式:崩溃恢复和消息广播。

当zk集群刚启动或者Leader(领导)服务宕机、重启或者网络故障无法与过半Follower服务器宝成通信时,所有服务器进入崩溃恢复模式,先选出新的领导者服务器,然后集群中Follower服务器与新的Leader服务器同步,当集群超过半数与leader服务器完成同步之后,退出恢复模式进入消息广播模式,leader开始接受客户端事务和请求并处理。

5.四种类型的数据节点znode

PERSISTENT:持久化目录节点(客户端与zk断开后,该节点依旧存在)

PERSISTENT_SEQUENTIAL:持久化顺序目录节点(同上,但是zk给节点名称进行顺序编号)

EPHEMERAL:临时目录节点(客户端与zk断开后,该节点被删除)

EPHEMERAL_SEQUENTIAL:临时顺序目录节点(同上,但是zk给节点名称进行顺序编号)

6.zookeeper Watcher机制–数据变更通知

zk允许客户端向服务端的某个znode注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,客户端根据Watcher通知状态和事件类型做出业务上的改变。

工作机制:

  • 客户端注册watcher
  • 服务端处理watcher
  • 客户端回调watcher

Watcher特性总结:

①一次性:客户端或服务端一旦Watcher触发,zk都会将对应的存储中删除,从而减轻服务端压力,否则对于频繁更新的节点,服务端不断向客户端发送通知,对双方压力都很大。

②客户端串行执行:客户端watcher毁掉的过程是串行同步的过程。

③轻量:watchetr只会告诉客户端发生了事件不会说明具体内容,客户端向服务端注册watcher时,不会把客户端真实的watcher对象实体传递到服务端,只是在客户端请求中使用boolean进行标记。

④watcher event异步发送watcher通知事件从server发送client是异步的,不同的客户端和服务器之间通过socket通信,由于网络或其他因素导致客户端不通的时刻监听到事件,由于zk本身特性,只有客户端坚挺到事件之后,才能监听到znode发生的变化,所以zk不能期望监控的节点每次的变化。zk只能保证最终一致性,无法保证强一致性。

⑤注册watcher getData、exists、getChildren

⑥触发watcher create、delete、setData

7.客户端注册wathcer实现

①调用getData()/getChildren()/exist()三个API,传入Watcher对象

②标记请求request,封装Watcher到WatchRegistration

③封装成Packet对象,发服务端发送request

④收到服务端响应后,将Watcher注册到ZKWatcherManager中进行管理

⑤请求返回,完成注册。

8.服务端处理watcher实现

①服务端接收Watcher并存储

接收到客户端请求,处理请求判断是否需要注册Watcher,需要的话将数据节点的节点路径和ServerCnxn(ServerCnxn代表一个客户端和服务端的连接,实现了Watcher的process接口,此时可以看成一个Watcher对象)存储在WatcherManager的WatchTable和watch2Paths中去。

②Watcher触发

以服务端接收到 setData() 事务请求触发NodeDataChanged事件为例:

  • 封装WatchedEvent

将通知状态(SyncConnected)、事件类型(NodeDataChanged)以及节点路径封装成一个WatchedEvent对象

  • 查询Watcher

从WatchTable中根据节点路径查找Watcher

  • 没找到;说明没有客户端在该数据节点上注册过Watcher
  • 找到;提取并从WatchTable和Watch2Paths中删除对应Watcher(从这里可以看出Watcher在服务端是一次性的,触发一次就失效了

③调用process方法来触发Watcher

process主要就是通过ServerCnxn对应的TCP连接发送Watcher事件通知。

9.客户端回调watcher

客户端SendThread线程接收事件通知,交由EventThread线程回调Watcher。客户端的Watcher机制同样是一次性的,一旦被触发后,该Watcher就失效了。

10.ACL(Access Control List)权限控制机制

包括三个方面:

  • 权限模式(Scheme)

    • IP:从IP地址粒度进行权限控制
    • Digest:最常用,用类似于 username:password 的权限标识来进行权限配置,便于区分不同应用来进行权限控制
    • World:最开放的权限控制方式,是一种特殊的digest模式,只有一个权限标识“world:anyone”
    • Super:超级用户
  • 授权对象

​ 授权对象指的是权限赋予的用户或一个指定实体,例如IP地址或是机器灯。

  • 权限 Permission

    • CREATE:数据节点创建权限,允许授权对象在该Znode下创建子节点
    • DELETE:子节点删除权限,允许授权对象删除该数据节点的子节点
    • READ:数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等
    • WRITE:数据节点更新权限,允许授权对象对该数据节点进行更新操作
    • ADMIN:数据节点管理权限,允许授权对象对该数据节点进行ACL相关设置操作

11.服务器角色

Leader:

  • 事务请求唯一调度和处理者,保证集群事务处理的有序性
  • 集群内部各个服务的调度这

Follower:

  • 处理客户端非事务请求,转发事务请求给Leader服务器
  • 参与事务请求Proposal投票
  • 参与Leader选举投票

Observer:

  • 3.3版本后引入的角色,不影响集群事务处理能力的基础上提升集群的非事务处理能力
  • 处理客户端非事务请求,转发事务请求给Leader服务器
  • 不参与任何形式的投票

12.zk中server工作状态

服务器具有四种状态,分别是LOOKING、FOLLOWING、LEADING、OBSERVING。

  • LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有Leader,因此需要进入Leader选举状态。
  • FOLLOWING:跟随者状态。表明当前服务器角色是Follower。
  • LEADING:领导者状态。表明当前服务器角色是Leader。
  • OBSERVING:观察者状态。表明当前服务器角色是Observer。

13.Leader选举

Leader选举是保证分布式数据一致性的关键所在。当Zookeeper集群中的一台服务器出现以下两种情况之一时,需要进入Leader选举。

(1) 服务器初始化启动。

(2) 服务器运行期间无法和Leader保持连接。

下面就两种情况进行分析讲解。

1. 服务器启动时期的Leader选举

若进行Leader选举,则至少需要两台机器,这里选取3台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器Server1启动时,其单独无法进行和完成Leader选举,当第二台服务器Server2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程。选举过程如下

(1) 每个Server发出一个投票。由于是初始情况,Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时Server1的投票为(1, 0),Server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。

(2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。

(3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下

· 优先检查ZXID。ZXID比较大的服务器优先作为Leader。

· 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。

对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时Server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于Server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。

(4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader。

(5) 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。

2. 服务器运行时期的Leader选举

在Zookeeper运行期间,Leader与非Leader服务器各司其职,即便当有非Leader服务器宕机或新加入,此时也不会影响Leader,但是一旦Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮Leader选举,其过程和启动时期的Leader选举过程基本一致。假设正在运行的有Server1、Server2、Server3三台服务器,当前Leader是Server2,若某一时刻Leader挂了,此时便开始Leader选举。选举过程如下

(1) 变更状态。Leader挂后,余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。

(2) 每个Server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定Server1的ZXID为123,Server3的ZXID为122;在第一轮投票中,Server1和Server3都会投自己,产生投票(1, 123),(3, 122),然后各自将投票发送给集群中所有机器。

(3) 接收来自各个服务器的投票。与启动时过程相同。

(4) 处理投票。与启动时过程相同,此时,Server1将会成为Leader。

(5) 统计投票。与启动时过程相同。

(6) 改变服务器的状态。与启动时过程相同。

14.Leader选举算法分析

在3.4.0后的Zookeeper的版本只保留了TCP版本的FastLeaderElection选举算法。当一台机器进入Leader选举时,当前集群可能会处于以下两种状态

· 集群中已经存在Leader。

· 集群中不存在Leader。

对于集群中已经存在Leader而言,此种情况一般都是某台机器启动得较晚,在其启动之前,集群已经在正常工作,对这种情况,该机器试图去选举Leader时,会被告知当前服务器的Leader信息,对于该机器而言,仅仅需要和Leader机器建立起连接,并进行状态同步即可。而在集群中不存在Leader情况下则会相对复杂,其步骤如下

(1) 第一次投票。无论哪种导致进行Leader选举,集群的所有机器都处于试图选举出一个Leader的状态,即LOOKING状态,LOOKING机器会向所有其他机器发送消息,该消息称为投票。投票中包含了SID(服务器的唯一标识)和ZXID(事务ID),(SID, ZXID)形式来标识一次投票信息。假定Zookeeper由5台机器组成,SID分别为1、2、3、4、5,ZXID分别为9、9、9、8、8,并且此时SID为2的机器是Leader机器,某一时刻,1、2所在机器出现故障,因此集群开始进行Leader选举。在第一次投票时,每台机器都会将自己作为投票对象,于是SID为3、4、5的机器投票情况分别为(3, 9),(4, 8), (5, 8)。

(2) 变更投票。每台机器发出投票后,也会收到其他机器的投票,每台机器会根据一定规则来处理收到的其他机器的投票,并以此来决定是否需要变更自己的投票,这个规则也是整个Leader选举算法的核心所在,其中术语描述如下

· vote_sid:接收到的投票中所推举Leader服务器的SID。

· vote_zxid:接收到的投票中所推举Leader服务器的ZXID。

· self_sid:当前服务器自己的SID。

· self_zxid:当前服务器自己的ZXID。

每次对收到的投票的处理,都是对(vote_sid, vote_zxid)和(self_sid, self_zxid)对比的过程。

规则一:如果vote_zxid大于self_zxid,就认可当前收到的投票,并再次将该投票发送出去。

规则二:如果vote_zxid小于self_zxid,那么坚持自己的投票,不做任何变更。

规则三:如果vote_zxid等于self_zxid,那么就对比两者的SID,如果vote_sid大于self_sid,那么就认可当前收到的投票,并再次将该投票发送出去。

规则四:如果vote_zxid等于self_zxid,并且vote_sid小于self_sid,那么坚持自己的投票,不做任何变更。

结合上面规则,给出下面的集群变更过程。

(3) 确定Leader。经过第二轮投票后,集群中的每台机器都会再次接收到其他机器的投票,然后开始统计投票,如果一台机器收到了超过半数的相同投票,那么这个投票对应的SID机器即为Leader。此时Server3将成为Leader。

由上面规则可知,通常那台服务器上的数据越新(ZXID会越大),其成为Leader的可能性越大,也就越能够保证数据的恢复。如果ZXID相同,则SID越大机会越大。

15.Leader选举实现细节

\1. 服务器状态

服务器具有四种状态,分别是LOOKING、FOLLOWING、LEADING、OBSERVING。

LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有Leader,因此需要进入Leader选举状态。

FOLLOWING:跟随者状态。表明当前服务器角色是Follower。

LEADING:领导者状态。表明当前服务器角色是Leader。

OBSERVING:观察者状态。表明当前服务器角色是Observer。

2. 投票数据结构

每个投票中包含了两个最基本的信息,所推举服务器的SID和ZXID,投票(Vote)在Zookeeper中包含字段如下

id:被推举的Leader的SID。

zxid:被推举的Leader事务ID。

electionEpoch:逻辑时钟,用来判断多个投票是否在同一轮选举周期中,该值在服务端是一个自增序列,每次进入新一轮的投票后,都会对该值进行加1操作。

peerEpoch:被推举的Leader的epoch。

state:当前服务器的状态。

3. QuorumCnxManager:网络I/O

每台服务器在启动的过程中,会启动一个QuorumPeerManager,负责各台服务器之间的底层Leader选举过程中的网络通信。

(1) 消息队列。QuorumCnxManager内部维护了一系列的队列,用来保存接收到的、待发送的消息以及消息的发送器,除接收队列以外,其他队列都按照SID分组形成队列集合,如一个集群中除了自身还有3台机器,那么就会为这3台机器分别创建一个发送队列,互不干扰。

· recvQueue:消息接收队列,用于存放那些从其他服务器接收到的消息。

· queueSendMap:消息发送队列,用于保存那些待发送的消息,按照SID进行分组。

· senderWorkerMap:发送器集合,每个SenderWorker消息发送器,都对应一台远程Zookeeper服务器,负责消息的发送,也按照SID进行分组。

· lastMessageSent:最近发送过的消息,为每个SID保留最近发送过的一个消息。

(2) 建立连接。为了能够相互投票,Zookeeper集群中的所有机器都需要两两建立起网络连接。QuorumCnxManager在启动时会创建一个ServerSocket来监听Leader选举的通信端口(默认为3888)。开启监听后,Zookeeper能够不断地接收到来自其他服务器的创建连接请求,在接收到其他服务器的TCP连接请求时,会进行处理。为了避免两台机器之间重复地创建TCP连接,Zookeeper只允许SID大的服务器主动和其他机器建立连接,否则断开连接。在接收到创建连接请求后,服务器通过对比自己和远程服务器的SID值来判断是否接收连接请求,如果当前服务器发现自己的SID更大,那么会断开当前连接,然后自己主动和远程服务器建立连接。一旦连接建立,就会根据远程服务器的SID来创建相应的消息发送器SendWorker和消息接收器RecvWorker,并启动。

(3) 消息接收与发送。消息接收:由消息接收器RecvWorker负责,由于Zookeeper为每个远程服务器都分配一个单独的RecvWorker,因此,每个RecvWorker只需要不断地从这个TCP连接中读取消息,并将其保存到recvQueue队列中。消息发送:由于Zookeeper为每个远程服务器都分配一个单独的SendWorker,因此,每个SendWorker只需要不断地从对应的消息发送队列中获取出一个消息发送即可,同时将这个消息放入lastMessageSent中。在SendWorker中,一旦Zookeeper发现针对当前服务器的消息发送队列为空,那么此时需要从lastMessageSent中取出一个最近发送过的消息来进行再次发送,这是为了解决接收方在消息接收前或者接收到消息后服务器挂了,导致消息尚未被正确处理。同时,Zookeeper能够保证接收方在处理消息时,会对重复消息进行正确的处理。

16.FastLeaderElection:选举算法核心

· 外部投票:特指其他服务器发来的投票。

· 内部投票:服务器自身当前的投票。

· 选举轮次:Zookeeper服务器Leader选举的轮次,即logicalclock。

· PK:对内部投票和外部投票进行对比来确定是否需要变更内部投票。

(1) 选票管理

· sendqueue:选票发送队列,用于保存待发送的选票。

· recvqueue:选票接收队列,用于保存接收到的外部投票。

· WorkerReceiver:选票接收器。其会不断地从QuorumCnxManager中获取其他服务器发来的选举消息,并将其转换成一个选票,然后保存到recvqueue中,在选票接收过程中,如果发现该外部选票的选举轮次小于当前服务器的,那么忽略该外部投票,同时立即发送自己的内部投票。

· WorkerSender:选票发送器,不断地从sendqueue中获取待发送的选票,并将其传递到底层QuorumCnxManager中。

(2) 算法核心

在这里插入图片描述

上图展示了FastLeaderElection模块是如何与底层网络I/O进行交互的。Leader选举的基本流程如下

1. 自增选举轮次。Zookeeper规定所有有效的投票都必须在同一轮次中,在开始新一轮投票时,会首先对logicalclock进行自增操作。

2. 初始化选票。在开始进行新一轮投票之前,每个服务器都会初始化自身的选票,并且在初始化阶段,每台服务器都会将自己推举为Leader。

3. 发送初始化选票。完成选票的初始化后,服务器就会发起第一次投票。Zookeeper会将刚刚初始化好的选票放入sendqueue中,由发送器WorkerSender负责发送出去。

4. 接收外部投票。每台服务器会不断地从recvqueue队列中获取外部选票。如果服务器发现无法获取到任何外部投票,那么就会立即确认自己是否和集群中其他服务器保持着有效的连接,如果没有连接,则马上建立连接,如果已经建立了连接,则再次发送自己当前的内部投票。

5. 判断选举轮次。在发送完初始化选票之后,接着开始处理外部投票。在处理外部投票时,会根据选举轮次来进行不同的处理。

· 外部投票的选举轮次大于内部投票。若服务器自身的选举轮次落后于该外部投票对应服务器的选举轮次,那么就会立即更新自己的选举轮次(logicalclock),并且清空所有已经收到的投票,然后使用初始化的投票来进行PK以确定是否变更内部投票。最终再将内部投票发送出去。

· 外部投票的选举轮次小于内部投票。若服务器接收的外选票的选举轮次落后于自身的选举轮次,那么Zookeeper就会直接忽略该外部投票,不做任何处理,并返回步骤4。

· 外部投票的选举轮次等于内部投票。此时可以开始进行选票PK。

6. 选票PK。在进行选票PK时,符合任意一个条件就需要变更投票。

· 若外部投票中推举的Leader服务器的选举轮次大于内部投票,那么需要变更投票。

· 若选举轮次一致,那么就对比两者的ZXID,若外部投票的ZXID大,那么需要变更投票。

· 若两者的ZXID一致,那么就对比两者的SID,若外部投票的SID大,那么就需要变更投票。

7. 变更投票。经过PK后,若确定了外部投票优于内部投票,那么就变更投票,即使用外部投票的选票信息来覆盖内部投票,变更完成后,再次将这个变更后的内部投票发送出去。

8. 选票归档。无论是否变更了投票,都会将刚刚收到的那份外部投票放入选票集合recvset中进行归档。recvset用于记录当前服务器在本轮次的Leader选举中收到的所有外部投票(按照服务队的SID区别,如{(1, vote1), (2, vote2)…})。

9. 统计投票。完成选票归档后,就可以开始统计投票,统计投票是为了统计集群中是否已经有过半的服务器认可了当前的内部投票,如果确定已经有过半服务器认可了该投票,则终止投票。否则返回步骤4。

10. 更新服务器状态。若已经确定可以终止投票,那么就开始更新服务器状态,服务器首选判断当前被过半服务器认可的投票所对应的Leader服务器是否是自己,若是自己,则将自己的服务器状态更新为LEADING,若不是,则根据具体情况来确定自己是FOLLOWING或是OBSERVING。

以上10个步骤就是FastLeaderElection的核心,其中步骤4-9会经过几轮循环,直到有Leader选举产生。

16.数据同步

集群完成Leader选举后,Learner(Follower和Observer的统称)向Leader服务器进行注册,当Learner服务器向Leader服务器完成注册后,进入同步环节。

数据同步流程:(均以消息传递的方式进行)

i. Learner向Learder注册

ii. 数据同步

iii. 同步确认

Zookeeper的数据同步通常分为四类

  • 直接差异化同步(DIFF同步)
  • 先回滚再差异化同步(TRUNC+DIFF同步)
  • 仅回滚同步(TRUNC同步)
  • 全量同步(SNAP同步)

在进行数据同步前,Leader服务器会完成数据同步初始化:

  • peerLastZxid:从learner服务器注册时发送的ACKEPOCH消息中提取lastZxid(该Learner服务器最后处理的ZXID)
  • minCommittedLog:Leader服务器Proposal缓存队列committedLog中最小ZXID
  • maxCommittedLog:Leader服务器Proposal缓存队列committedLog中最大ZXID

直接差异化同步(DIFF同步)

场景:peerLastZxid介于minCommittedLog和maxCommittedLog之间
在这里插入图片描述
先回滚再差异化同步(TRUNC+DIFF同步)

场景:当新的Leader服务器发现某个Learner服务器包含了一条自己没有的事务记录,那么就需要让该Learner服务器进行事务回滚–回滚到Leader服务器上存在的,同时也是最接近于peerLastZxid的ZXID

仅回滚同步(TRUNC同步)

场景:peerLastZxid 大于 maxCommittedLog

全量同步(SNAP同步)

场景一:peerLastZxid 小于 minCommittedLog

场景二:Leader服务器上没有Proposal缓存队列且peerLastZxid不等于lastProcessZxid

17.zookeeper如何保证事务的顺序一致性

zk采用全局递增的事务Id来标识,所有的proposal(提议)都在被提出的时候加上了zxid,zxid实际上就是一个64位的数字,高32位是epoch(时期; 纪元; 世; 新时代)用来标识leader周期,如果有新的leader产生出来,epoch会自增,低32位用来递增计数。当新产生proposal的时候,会依据数据库的两阶段过程,首先会向其他的server发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。

18.分布式集群为什么会有Master

分布式环境中,有些业务逻辑只需集群中的某一台机器执行,其他机器可以共享结果,可以减少重复计算,提高性能,于是就需要进行leader选举。

19.zk节点宕机如何处理

①zk本身是集群,推荐配置不少于3台服务器。zk自身保证当一个节点宕机时,其他节点继续服务。

②如一个Follower宕机,还有2台服务器提供访问,因为zk上数据是有多个副本,数据不会丢失。

③如果Leader宕机,zk会选举出新的leader。

④zk集群机制是超过一半数的节点正常,集群就能提供服务,当zk节点超过一半宕机,集群会失效。

⑤3个节点可以挂掉1个节点(leader可以得到2票>1.5)

⑥2个节点就不能挂掉任何1个节点(leader可以得到1票<=1)

20.zookeeper负载均衡和nginx负载均衡的区别

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

21.zookeeper有哪几种部署模式

  • 单机模式:
  • 为集群模式:
  • 集群模式:

22.集群最少要几台,集群的规则

集群规则为2N+1台,N>0,即3台(集群为奇数的原因是因为leader选举做铺垫,过半机制)

23.集群支持动态添加机器

就是水平扩容,zk这方面做的不好。两种方式:

  • 全部重启:关掉所有zk服务,修改配置之后启动,不影响之前客户端的会话。
  • 逐个重启:在过半存活可用的原则下,一台机器重启不影响集群对外提供服务,最常用的方式。

注:3.5版本开始支持动态扩容。

24.zookeeper对节点的watch监听通知是否永久,为什么不是永久

不是永久的,一个watch事件是一个一次性触发器,当被设置了Watch的数据发生了改变时,服务器将这个改变发送给watch客户端,通知他们。

为什么不是永久的:如果服务器频繁变动,而监听的客户端很多的情况下,每次变动都要通知所有客户端,给网络和服务器带来压力,产生惊群/羊群效应。

一般客户端执行getData("/节点A",true),如果节点A发生变更或删除,客户端会得到watch事件,但之后节点A在发生变更,而客户端有没有设置watch事件,就不在给客户端发送。

实际中,我们的客户端不需要知道服务端的每次变动,只要最新的数据即可。

25.zookeeper的java客户端有哪些

java客户端:zk自带的zkClient和Apache开源的Curator。

26.zookeeper常用命令

ls、get、set、create、delete

27.ZAB和Paxos算法的关系和区别

相同点:

都存在一个类似于Leader进程的角色,负责协调多个Follower进程运行

Leader进程都会等待超过半数的Follower做出正确反馈,才会将一个提案提交

ZAB协议中,每个Proposal中都包含一个epoch值代表当前Leader周期,Paxos中名字为Ballot

不同点:

ZAB用来构建高可用分布式数据主备系统(zk),Paxos用来构建分布式一致性状态系统。

28.zookeeper经典应用场景

zk是经典的发布/订阅模式的分布式数据管理与协调框架,开发人员可以使用它来进行分布式数据的发布和订阅。通过zk丰富的数据节点进行交叉使用,配合Watcher事件通知机制,可以方便的构建一系列分布式应用。重点涉及的核心功能,如:

  • 数据发布/订阅
  • 负载均衡
  • 命名服务
  • 分布式协调/通知
  • 集群管理
  • Master选举
  • 分布式锁
  • 分布式队列

①发布/订阅:就是配置中心,发布者发布提供订阅者进行数据订阅。

动态获取信息(配置信息)

实现数据的集中式管理和数据的动态更新

基于zk的实现方式:

1)数据存储:将数据存储到zk上的一个数据节点

2)数据获取:应用在启动初始化节点从zk数据节点读取数据,并在该节点注册一个数据变更Watcher

3)数据变更:数据变化时,更新zk对应节点数据,zk会将数据变更通知发到各客户端,客户端接到通知后重新读取变更后的数据。

②负载均衡:

①zk的命名服务

命名服务是指通过指定的名字来获取资源或者服务的地址,利用zk创建一个全局的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。

②分布式通知和协调

对于系统调度来说:操作人员发送通知实际是通过控制台改变某个节点的状态,然后zk将这些变化发送给注册了这个节点的watcher的所有客户端。

对于执行情况汇报:每个工作进程都在某个目录下创建一个临时节点。并携带工作的进度数据,这样汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局情况。

③zk的命名服务(文件系统)

命名服务是指通过指定的名字来获取资源或者服务的地址,利用zk创建一个全局的路径,即是唯一的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。

④zk的配置管理(文件系统、通知机制)

程序分布式的部署在不同的机器上,将程序的配置信息放在zk的znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用watcher通知给各个客户端,从而更改配置。

⑤Zookeeper集群管理(文件系统、通知机制)

所谓集群管理无在乎两点:是否有机器退出和加入、选举master。

对于第一点,所有机器约定在父目录下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。

新机器加入也是类似,所有机器收到通知:新兄弟目录加入,highcount又有了,对于第二点,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。

⑥Zookeeper分布式锁(文件系统、通知机制)

有了zookeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

对于第一类,我们将zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的distribute_lock 节点就释放出锁。

对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。

⑦Zookeeper队列管理(文件系统、通知机制)

两种类型的队列:

1、同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。

2、队列按照 FIFO 方式进行入队和出队操作。

第一类,在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。

第二类,和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。在特定的目录下创建PERSISTENT_SEQUENTIAL节点,创建成功时Watcher通知等待的队列,队列删除序列号最小的节点用以消费。此场景下Zookeeper的znode用于消息存储,znode存储的数据就是消息队列中的消息内容,SEQUENTIAL序列号就是消息的编号,按序取出即可。由于创建的节点是持久化的,所以不必担心队列消息的丢失问题。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐