zoukankan      html  css  js  c++  java
  • Zookeeper

    一、Zookeeper的理论基础

    1、Zookeeper简介

    Zookeeper由雅虎研究院开发,后捐给Apache。是一个开源的分布式应用程序协调服务器,为分布式系统提供一致性服务。其一致性是通过基于Paxos算法的ZAB协议完成的。其主要功能包括:配置维护、域名服务、分布式同步、集群管理等。

    2、一致性

    Zookeeper是如何保证分布式系统的一致性的呢?是因为Zookeeper具有以下几方面的特点:

    • 顺序一致性:从同一个客户端发起的多个事务请求(写操作请求),最终会被严格按照发起顺序记录到Zookeeper中。
    • 原子性:所有事务请求的结果在集群中所有主机上的应用结果都是一致的。要么都应用成功,要么都失败。
    • 单一视图:无论Client连接的是Zookeeper集群中的哪台主机,其看到的数据模型都是一致的。
    • 可靠性:一旦Zookeeper主机成功应用了某个事务,则其所引发的服务器状态变化会被一直保留下来,直到另一个事务将其改变。
    • 最终一致性:一旦一个事务被成功应用,Zookeeper可以保证在一段很短的时间后,客户端最终可以从任意的Zookeeper主机上读取到最新的数据。但不能保证实时读到。

    3、Paxos算法

    对于Zookeeper理论的学习,最重要也是最难的知识点就是Paxos算法。所以先学这个Paxos算法。

    (1)算法简介

    Paxos算法是Leslie Lamport在1990年提出的一种基于消息传递的、具有高容错性的一致性算法。Paxos算法是一种公认的晦涩难懂的算法,并且在工程实现上也具有很大难度。较有名的Paxos工程实现有Google Chubby、ZAB、微信的PhxPaxos等。

    Paxos算法是用于解决什么问题的呢?解决在分布式系统中如何就某个决议达成一致。

    (2)Paxos算法与拜占庭将军问题

    拜占庭将军问题是由Paxos算法作者Leslie Lamport提出的点对点通信中的基本问题。该问题要说明的含义是,在不可靠信道上试图通过消息传递的方式达到一致性是不可能的(因为消息可能被截获篡改)。所以,Paxos算法的前提是不存在拜占庭将军问题,即信道是安全的、可靠的,集群节点间传递的消息是不会被篡改的

    一般情况下,分布式系统中各个节点间采用两种通讯模型:共享内存、消息传递。而Paxos算法是基于消息传递通讯模型的。

    (3)算法描述

    ① 三种角色

    在Paxos算法中有三种角色,分别具有三种不同的行为。但很多时候,一个进程可能同时充当着多种角色。

    • Proposer:提案者
    • Acceptor:表决者
    • Learner:学习者(同步者

    ② Paxos算法的一致性

    A. 这里先看这样一个场景,便于后续理解:

    a. 有一个叫做Paxos的小岛(Island)上面住了一批居民,岛上面所有的事情由一些特殊的人决定,他们叫做议员(Senator)。
    b. 议员的总数(SenatorCount)是确定的,不能更改。
    c. 岛上每次环境事务的变更都需要通过一个提议(Proposal),每个提议都有一个编号(PID),这个编号是一直增长的,不能倒退。
    d. 每个提议都需要超过半数((SenatorCount)/2+1)的议员同意才能生效。
    e. 每个议员只会同意大于当前编号的提议,包括已生效的和未生效的。
    f. 如果议员收到小于等于当前编号的提议,他会拒绝,并告知对方:你的提议已经有人提过了。这里的当前编号是每个议员在自己记事本上面记录的编号,他不断更新这个编号。整个议会不能保证所有议员记事本上的编号总是相同的。
    现在议会有一个目标:保证所有的议员对于提议都能达成一致的看法。(即提议需要超过半数)

    好,现在议会开始运作,所有议员一开始记事本上面记录的编号都是0。 

    有一个议员发了一个提议:
    将电费设定为1元/度。他首先看了一下记事本,嗯,当前提议编号是0,那么我的这个提议的编号就是1,于是他给所有议员发消息:1号提议,设定电费1元/度。其他议员收到消息以后查了一下记事本,哦,当前提议编号是0,这个提议可接受,于是他记录下这个提议并回复:我接受你的1号提议。同时他在记事本上记录:当前提议编号为1。这时如果发起提议的议员收到了超过半数的回复,则立即给所有人发通知:1号提议生效!收到的议员会修改他的记事本,将1号提议由记录改成正式的法令,当有人问他电费为多少时,他会查看法令并告诉对方:1元/度。
    现在看冲突的解决:

    假设总共有三个议员S1-S3,S1和S2同时发起了一个提议:1号提议,设定电费。S1想设为1元/度,S2想设为2元/度。结果S3先收到了S1的提议,于是他做了和前面同样的操作。紧接着他又收到了S2的提议,结果他一查记事本,咦,这个提议的编号小于等于我的当前编号1,于是他拒绝了这个提议:对不起,这个提议先前提过了。于是S2的提议被拒绝,S1正式发布了提议:1号提议生效。S2向S1或者S3打听并更新了1号法令的内容,然后他可以选择继续发起2号提议。 

    B. Paxos算法的一致性主要体现在以下几点:

    • 每个提案者(Proposer)在提出提案时首先都会获取到一个具有全局唯一性的、递增的提案编号N,即在整个集群中是唯一的编号N,然后将该编号赋予其要提出的提案。
    • 每个表决者(Acceptor)在accept某提案后,会将该提案的编号N记录在本地,这样每个表决者中保存的已经被accept的提案中会存在一个编号最大的提案,假设其编号为maxN。每个表决者仅会accept编号大于自己本地maxN的提案。
    • 在众多提案中最终只能有一个提案被选定。
    • 一旦一个提案被选定,则其他服务器会主动同步(Learn)该提案到本地。  
    • 没有提案被提出则不会有提案被选定。

    ③ 算法描述过程

    Paxos算法的执行过程划分为两个阶段:准备阶段prepare、接受阶段accept。

    A. prepare阶段

    a. 提案者(Proposer)准备提交一个编号为N的提议,于是其首先向所有表决者(Acceptor)发送prepare(N)请求,用于试探集群是否支持该编号的提议。

    b. 每个表决者中都保存着自己曾经accept过的提议中的最大编号maxN。当一个表决者接收到其它主机发送来的prepare(N)请求时,会比较N与maxN的值。有以下几种情况:

    a) 若N小于maxN,则说明该提议已过时,当前表决者采取不回应或者回应Error的方式来拒绝该prepare请求;

    b) 若N大于maxN,则说明该提议是可以接受的,当前表决者会首先将该N记录下来,并将其曾经已经accept的编号最大的提案Proposal(myid, maxN, value)反馈给提案者,以向提案者展示自己支持的提案意愿。其中第一个参数myid表示表决者的标识id,第二个参数表示其曾接受的提案的最大编号maxN,第三个参数表示该提案的真正内容value。当然,若当前表决者还未曾accept过任何提议,则会将Proposal(myid, null, null)反馈给提案者。

    c) 在prepare阶段N不可能等于maxN。这是由N的生成机制决定的。要获取N的值,其必定会在原来数值的基础上采用同步锁方式增一。

    B. accept阶段

    a. 当提案者(Proposer)发出prepare(N)后,若收到了超过半数的表决者(Acceptor)的反馈,那么该提案者就会将其真正的提案Proposal(myid, N, value)发送给所有的表决者。

    b. 当表决者接收到提案者发送的Proposal(myid, N, value)提案后,会再次拿出自己曾经accept过的提议中的最大编号maxN或曾经记录下的prepare的最大编号,让N与它们进行比较,若N大于等于这两个编号,则当前表决者accept该提案,并反馈给提案者。若N小于这两个编号,则表决者采取不回应或回应Error的方式来拒绝该提议。

    c. 若提案者没有接收到超过半数的表决者的accept反馈,则有两种可能的结果产生。一是放弃该提案,不再提出;二是重新进入prepare阶段,递增提案号,重新提出prepare请求。

    d. 若提案者接收到的反馈数量超过了半数,则其会向外广播两类信息:

    a) 向曾accept其提案的表决者发送“可执行数据同步信号”,即让它们执行其曾接收到的提案;

    b) 向未曾向其发送accept反馈的表决者发送“提案 + 可执行数据同步信号”,即让接收到该提案后立马执行。

    例子:S1、S2、S3都想发出提议:

    这个时候S1和S3超过半数,将会进入accept阶段;而S2没有超半数,因此要么放弃,要么再次发出提议,假设当S2再次发出提议,将会:

    接着进入accept阶段:

    (4)Paxos算法的活锁问题

    活锁:指的是任务或者执行者没有被阻塞,但由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开,死锁则不行。

    活锁问题演示:

    初始状态:

    ProposerA提交一个提案,编号为1,现在处于accept阶段:

    这个时候还没等到ProposerA完成accept阶段,ProposerB提交一个提案,编号为2,现在B也处于accept阶段:

     

    这个时候还没等到ProposerB完成accept阶段,ProposerA重新提交一个提案,编号为3,现在A也处于accept阶段:

    ProposerB又重新提交提案......就这样无限循环下去,出现了活锁问题 。

    前面所述的Paxos算法在实际应用过程中,根据不同的实际需求存在诸多不便之处,例如活锁,所以也就出现了很多改进Paxos算法的优化算法,例如MultiPaxos、 Fast Paxos、EPaxos。

    Fast Paxos算法的改进:只允许一个进程提交提案(只允许一个Proposer提交提案),即该进程具有对N的唯一操作权。该方式解决了活锁问题。

    4、ZAB协议

    (1)ZAB简介

    ZAB,全称Zookeeper Atomic Broadcast,Zookeeper原子消息广播协议,是专为Zookeeper设计的一种支持崩溃恢复的原子广播协议,在Zookeeper中,主要依赖ZAB协议来实现分布式数据一致性

    Zookeeper使用一个单一主进程来接收并处理客户端的所有事务请求,即写请求。当服务器数据的状态发生变更后,集群采用ZAB原子广播协议,以事务提案Proposal的形式广播到所有的副本进程上。ZAB协议能够保证一个全局的变更序列,即可以为每一个事务分配一个全局的递增编号xid。

    当Zookeeper客户端连接到Zookeeper集群的一个节点后,若客户端提交的是读请求,那么当前节点就直接根据自己保存的数据对其进行响应;如果是写请求且当前节点不是Leader,那么节点就会将该写请求转发给Leader,Leader会以提案的方式广播该写操作,只要有超过半数节点同意该写操作,则该写操作请求就会被提交。然后Leader会再次广播给所有订阅者,即Learner,通知它们同步数据。

     

    (2)ZAB协议和Paxos算法的关系

    ZAB协议是Paxos算法的一种工业实现算法。但两者的设计目标不太一样。ZAB协议主要用于构建一个高可用的分布式数据主从系统(主从和主备不一样。主从是主和从节点都干活;主备是主干活,备是备用、暂不干活),即Follower是Leader的从机,Leader挂了,马上就可以选举出一个新的Leader,但平时它们都对外提供服务。而Fast Paxos算法则是用于构建一个分布式一致性状态机系统,确保系统中各个节点的状态都是一致的。

    另外,ZAB还使用Google的Chubby算法作为分布式锁的实现,而Google的Chubby也是Paxos算法的应用。

    (3)三类角色

    为了避免Zookeeper的单点问题,Zookeeper也是以集群的形式出现的。Zookeeper集群中的角色主要有以下三类:

    • Leader:接收和处理客户端的读请求;Zookeeper集群中事务请求的唯一处理者,并负责发起决议和投票,然后将通过的事务请求在本地进行处理后,将处理结果同步给集群中的其它主机。
    • Follower:接收和处理客户端的读请求;将事务请求转给Leader;同步Leader中的数据;当Leader挂了,参与Leader的选举(具有选举权和被选举权)。
    • Observer:没有选举权和被选举权的Follower(临时工)。若Zookeeper集群中的读压力很大,则需要增加Observer,最好不要增加Follower。因为增加Follower将会增大投票与统计投票的压力以及Leader选举效率。

    这三类角色在不同的情况下又有一些不同的名称:

    • Learner:Follower + Observer
    • QuorumServer:Follower + Leader

    (4)三个数据

    在ZAB协议中有三个很重要的数据:

    • zxid:是一个64位长度的Long类型。其中高32位表示epoch,低32位表示xid。
    • epoch:每个Leader都具有一个不同的epoch,用于区分不同的时期。
    • xid:事务id,是一个流水号,只会增加。

    (5)三种模式

    ZAB协议中对zkServer的状态描述有三种模式。这三种模式并没有十分明显的界线,它们相互交织在一起。

    • 恢复模式:当集群启动或当Leader挂了时,系统需要进入恢复模式,以恢复系统对外提供服务的能力。其中包含两个很重要的阶段:Leader的选举和初始化同步。
    • 广播模式:分为初始化广播和更新广播。(站在Leader角度)
    • 同步模式:分为初始化同步和更新同步。(站在Follower角度)

    (6)四种状态

    Zookeeper集群中的每一台主机在不同的阶段会处于不同的状态。每一台主机具有四种状态。

    • LOOKING:选举状态
    • FOLLOWING:Follower的正常工作状态,从Leader同步数据的状态
    • OBSERVING:Observer的正常工作状态,从Leader同步数据的状态
    • LEADING:Leader的正常工作状态,Leader广播数据更新的状态

    (7)同步模式与广播模式

    ① 初始化同步

    前面我们说过,恢复模式具有两个阶段:Leader选举和初始化同步。当完成Leader选举后,此时的Leader还是一个准Leader,要经过初始化同步后才能变成真正的Leader。

    具体过程如下:

    a. 为了保证Leader向Learner发送的提案有序,Leader会为每一个Learner服务器准备一个队列;

    b. Leader将那些没有被各个Learner同步的事务封装为Proposal;

    c. Leader将这些Proposal逐条发给各个Learner,并在每一个Proposal后都紧跟一个COMMIT消息,表示该事务已经被提交,Learner可以直接接收并执行;

    d. Learner接收到来自于Leader的Proposal,并将其更新到本地;

    e. 当Learner更新成功后,会向准Leader发送ACK消息;

    f. Leader服务器在收到来自Learner的ACK后会将该Learner加入到真正可用的Follower队列或Observer队列中。没有反馈ACK或反馈了但Leader没有收到的Learner,Leader不会将其加入到响应列表。

    ② 消息广播算法

    当集群中的Learner完成了初始化状态同步,那么整个Zookeeper集群就进入到正常工作模式了。

    如果集群中的Learner节点收到客户端的事务请求(写操作请求),那么这些Learner会将请求转发给Leader服务器。然后再执行如下的具体过程:

    a. Leader接收到事务请求后,为事务赋予一个全局唯一的64位自增id,即zxid,通过zxid的大小比较即可实现事务的有序性管理,然后将事务封装为一个Proposal;

    b. Leader根据Follower队列获取所有的Follower,然后再将Proposal通过这些Follower的队列将提案发送给各个Follower;

    c. 当Follower接收到提案后,先会将提案的zxid与本地记录的事务日志的最大的zxid进行比较。若当前提案的zxid大于最大的zxid,则将当前提案记录到本地事务日志中,并向Leader返回一个ACK;

    d. 当Leader接收到过半的ACK后,Leader就会向所有Follower的队列发送COMMIT消息,向所有Observer的队列发送Proposal;

    e. 当Follower收到COMMIT消息后,就会将日志中的事务正式更新到本地。当Observer收到Proposal后,会直接将事务更新到本地;

    f. 无论是Follower还是Observer,在同步完成后都需要向Leader发送成功ACK。

    ③ Observer的数量问题

    Observer数量一般与Follower数量相同。并不是Observer越多越好,因为Observer数量的增多虽不会增加事务操作压力,但其需要从Leader同步数据,Observer同步数据的时间是小于等于Follower同步数据的时间的。当Follower同步数据完成,Leader的Observer队列中的Observer主机将结束同步。那些完成同步的Observer将会进入到另一个对外提供服务的队列中。那么,那些没有同步了数据无法提供服务的Observer主机就形成了资源浪费。

    所以,对于事务操作发生比较频繁的系统,不建议使用过多的Observer。

    Leader中对于Observer存在两个队列:all(包含所以的Observer主机)、service(每发生一次事务更新,service队列就会变化一次)。

    (8)恢复模式的两个原则

    当集群正在启动过程中或Leader与超过半数的主机断连后,集群就进入了恢复模式。对于要恢复的数据状态需要遵循两个原则。

    ① 已被处理过的消息不能丢

    当Leader收到超过半数Follower的ACK后,就向各个Follower广播COMMIT消息,批准各个Server执行该写操作事务。当各个Server在接收到Leader的COMMIT消息后就会在本地执行该写操作,然后会向客户端响应写操作成功。

    但是如果在非全部Follower收到COMMIT消息之前Leader就挂了,这将导致一种后果:部分Server已经执行了该事务,而部分Server尚未收到COMMIT消息,所以其并没有执行该事务。当新的Leader被选出来,集群经过恢复模式后需要保证所有Server上都执行了那些已经被部分Server执行过的事务。

    ② 被丢弃的消息不能再现

    当在Leader新事务已经通过、其已经将该事务更新到了本地,但所有的Follower都还没收到COMMIT之前,Leader宕机了(比前面叙述的宕机更早),此时,所有的Follower根本不知道有该Proposal的存在。当新的Leader被选举出来、整个集群进入正常服务状态后,之前挂了的Leader主机重新启动并注册成为了Follower。若那个别人根本不知道的Proposal还保留在那个主机,则其数据就会比其它主机多出了内容,导致整个系统状态的不一致。Zookeeper最大的特点就是一致性,所以该Proposal应该被丢弃。类似这样应该被丢弃的事务,是不能再次出现在集群中的,应该被清除(递归,和Leader对比)。

    Zookeeper不能保证事务请求不丢失,但能保证一致性。

    (9)Leader选举

    在集群启动过程中或Leader宕机后,集群就进入了恢复模式。恢复模式中最重要的阶段就是Leader选举。

    ① Leader选举中的概念

    myid:这是Zookeeper集群中服务器的唯一标识,称为myid。例如,有三个Zookeeper服务器,那么编号分别是1、2、3。

    逻辑时钟:是一个整形数,该概念在选举时称为logicallock,而在选举结束后称为epoch。即epoch和logicallock是同一个值在不同情况下的不同名称。

    ② Leader选举算法

    在集群启动过程中的Leader选举过程(算法)和Leader断连后的Leader选举过程总体基本相同,但稍微有一些区别。 

    A、集群启动时的Leader选举

    若进行Leader选举,则至少需要两台主机,这里以三台主机组成的集群为例。

    B、宕机后的Leader选举

    在Zookeeper运行期间,Leader与非Leader服务器各司其职,即便当有非Leader服务器宕机或新加入时也不会影响Leader。但是若Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮的Leader选举,其过程和启动时期的Leader选举过程基本一致。

    5、CAP定理

    (1)CAP简介

    CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    • 一致性(C):分布式系统中多个主机之间是否能够保持数据一致性的特性。即当系统数据发生更新操作后,各个主机中的数据仍然处于一致的状态。
    • 可用性(A):系统提供的服务必须一直处于可用的状态,即对于用户的每一个请求,系统总是可以在有限的时间内对用户做出响应。
    • 分区容错性(P):分布式系统在遇到任何网络分区故障时,仍能够保证对外提供满足一致性和可用性的服务。

    对于分布式系统,网络环境相对是不可控的,出现网络分区是不可避免的,因此系统必须具有分区容错性。但其并不能同时保证一致性和可用性。CAP定理对于一个分布式系统来说,只可能满足两项,即要么CP,要么AP。

    (2)BASE理论

    BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写。BASE是对CP与AP权衡的结果。

    BASE理论的核心思想是:即使无法做到强一致性,但每个系统都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

    ① 基本可用:指分布式系统在出现不可预知故障的时候,允许损失部分可用性。

    ② 软状态:指允许系统数据存在的中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统主机间进行数据同步的过程存在一定延时。软状态,其实就是一种灰度状态,过度状态。

    ③ 最终一致性:强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

    (3)Zookeeper遵循的是CP

    Zookeeper保证了一致性,但牺牲了可用性。体现在哪里呢?(Eureka相反,它遵循AP,保证可用性,牺牲一致性)

    当Leader宕机后,Zookeeper集群会马上进行新的Leader的选举。但选举时长一般在200毫秒内,最长不超过60秒,整个选举期间Zookeeper集群是不接受客户端的读写操作的,即Zookeeper集群此时处于瘫痪状态。所以,其不满足可用性。

    (4) Zookeeper可能会存在脑裂

    脑裂可以简单理解为一个集群出现多个Leader。(出现多个脑子......)

    在多机房部署一个集群中(解决分区容错性问题,一般一个机房部署的主机节点数会小于全部主机的半数,这样才会投票过半),若出现了网络连接问题,形成了多个分区,则可能出现脑裂问题,可能会导致数据不一致。

    下面这种情况不会产生脑裂,不会重新选举Leader:

    下面这种情况会产生脑裂,B和C之间会重新选举一个Leader:

    怎么解决脑裂问题?赶紧修复重启机房啊...三个都断俩了。

    另外,下面这种情况也不会出现脑裂:

    二、Zookeeper的安装与集群搭建

    三、Leader的选举机制

    四、Zookeeper技术内幕

    1、重要理论

    (1)数据模型znode

    Zookeeper数据存储结构与标准的Unix文件系统非常类似,都是在根节点下挂很多子节点。Zookeeper中没有引入传统文件系统中目录与文件的概念,而是使用了称为znode的数据节点概念。znode是Zookeeper中数据的最小单元,每个znode上都可以保存数据,同时还可以挂载子节点,形成一个树形化命名空间。

    ① 节点类型

    • 持久节点
    • 持久顺序节点
    • 临时节点:临时节点的生命周期与客户端的会话绑定在一起。临时节点不能有子节点,即临时节点只能是叶子节点。
    • 临时顺序节点 

    ② 节点状态

    cZxid:Created Zxid,表示当前znode被创建时的事务ID。

    ctime:Created Time,表示当前znode被创建的时间。

    mZxid:Modified Zxid,表示当前znode最后一次被修改时的事务ID。

    mtime:Modified Time,表示当前znode最后一次被修改时的时间。

    pZxid:表示当前znode的子节点列表最后一次被修改时的事务ID。注意,只能是其子节点列表变更了才会引起pZxid的变更,子节点内容的修改不会影响pZxid。

    cversion:Children Version,表示子节点的版本号。该版本号用于充当乐观锁。

    dataVersion:表示当前znode数据的版本号。该版本号用于充当乐观锁。

    aclVersion:表示当前znode的权限ACL的版本号。该版本号用于充当乐观锁。

    ephemeralOwner:若当前znode是持久节点,则其值为0;若为临时节点,则其值为创建该节点的会话的SessionID。当会话消失后,会根据SessionID来查找与该会话相关的临时节点进行删除。

    dataLength:当前znode中存放的数据的长度。

    numChildren:当前znode所包含的子节点的个数。

    (2)会话

    会话是Zookeeper中最重要的概念之一,客户端与服务端之间的任何交互操作都与会话相关。Zookeeper客户端启动时,首先会与Zookeeper服务器建立一个TCP长连接。连接一旦建立,客户端会话的生命周期也就开始了。

    ① 会话状态

    常见的会话状态有三种:

    • CONNECTING:连接中。客户端要创建连接,首先会在客户端创建一个Zookeeper对象,代表了服务器。其会采用轮询的方式对服务器列表逐个尝试连接,直到连接成功。不过,为了对Server进行负载均衡,其会首先对服务器列表进行打散操作,然后再轮询。
    • CONNECTED:已连接
    • CLOSED:连接已经关闭

    ② 会话连接事件

    客户端与服务端的长连接失效后,客户端将进行重连。在重连过程中客户端会产生三个会话连接事件:

    • CONNECTION_LOSS:连接丢失。因为网络抖动等原因导致连接中断,在客户端会引发连接丢失事件。该事件会触发客户端逐个尝试重新连接服务器,直到连接成功或超时为止。
    • SESSION_MOVED:会话转移。当连接丢失后,在SessionTimeout内重连成功,则SessionId是不变的,意味着临时节点也不会消失。若两次连接上的Server不是同一个,则会引发会话转移事件,该事件会引发客户端更新本地Zookeeper对象中的相关信息(如更新连接的Server的IP地址)。
    • SESSION_EXPIRED:会话失效。若客户端在SessionTimeout内没有连接成功,则服务器会将该会话进行清除,并向Client发送通知。但在Client收到通知之前又连接上了Server,此时的这个会话是失效的,会引发会话失效事件。该事件会触发客户端重新生成新的SessionId重连Server。

    ③ 会话连接超时管理--分桶策略

    前面无论是会话状态还是会话连接事件,都与会话超时有着紧密的联系。Zookeeper中对于会话的超时管理,采用了 一种特殊的方式——分桶策略。

    A、基本概念

    分桶策略:指将超时时间相近的会话放在同一个桶中来进行管理,以减少管理的复杂度。在检查超时时,只需要检查桶中剩下的会话即可,因为没有超时的会话已经被移出了桶,而桶中存在的会话就是超时的会话。

    从以下描述可知,Zookeeper对于会话的超时管理并非是精确的管理,即并非是一超时马上就执行相关的超时操作。

    桶就是一个时间窗口,在这个时间窗口结束后,就去检查在这时间窗口内是否有超时的Session。

    B、分桶依据

    分桶的计算依据为:

    ExpirationTime = CurrentTime + SessionTimeout

    Bucket = ExpirationTime / ExpirationInterval + 1

    ( BucketTime = (ExpirationTime / ExpirationInterval + 1) * ExpirationInterval )

    从以上公式可知,一个桶的大小为ExpirationInterval时间。只要ExpirationTime落入到同一个桶中,系统就会对其中的会话超时进行统一管理。(一个桶里只判断一次Session连接是否超时。超时的话,如果客户端重连成功就会生成新的Session)

    (3)ACL

    ① ACL简介

    ACL全称为Access Control List(访问控制列表),是一种细粒度的权限管理策略,可以针对任意用户与组进行细粒度的权限控制。Zookeeper利用ACL控制znode节点的访问权限,如节点数据读写、节点创建、节点删除、读取子节点列表、设置节点权限等。

    (Windows、Linux是采用UGO(User Group Other)来管理权限,属于粗粒度权限管理)

    ② Zookeeper的ACL维度

    Unix / Linux系统的ACL分为两个维度:组与权限,且目录的子目录或文件能够继承父目录的ACL的。而Zookeeper的ACL分为三个维度:授权策略scheme、授权对象id、用户权限permission,子znode不会继承父znode的权限。

    A 授权策略scheme

    用于确定权限验证过程中使用的检验策略(简单说就是,通过什么来验证权限,即一个用户要访问某个znode,如何验证其身份),在Zookeeper中最常用的有四种策略。

    • IP:根据IP进行验证。
    • digest:根据用户名与密码进行验证。
    • world:对所有用户不做任何验证。
    • super:超级用户,对任意节点具有任意操作权限。

    B 授权对象id 

    授权对象指的是权限赋予的用户。不同的授权策略具有不同类型的授权对象。下面是各个授权模式对应的授权对象id。

    • IP:将权限授予指定的IP。
    • digest:将权限授予具有指定用户名密码的用户。
    • world:将权限授予一个用户anyone(用户默认就是anyone)。
    • super:将权限授予具有指定用户名密码的用户。

    C 权限Permission

    权限指的是通过验证的用户可以对znode执行的操作。共有五种权限,另外Zookeeper支持自定义权限。

    • c:Create,允许授权对象在当前节点下创建子节点。
    • d:Delete,允许授权对象删除当前节点。
    • r:Read,允许授权对象读取当前节点的数据内容及子节点列表。
    • w:Write,允许授权对象修改当前节点数据内容及子节点列表(可以为当前节点增/删子节点)。
    • a:Acl,允许授权对象对当前节点进行ACL设置。

    (4)Watcher机制

    Zookeeper通过Watcher机制实现了发布/订阅模式。

    ① Watcher工作原理

    ② Watcher事件

    对于同一个事件类型,在不同的通知状态中代表的含义是不同的。

    ③ Watcher特性

    Zookeeper的Watcher机制具有以下几个特性

    • 一次性:Watcher机制不适合监听变化非常频繁的场景。
    • 串行性:对于同一个节点相同事件类型的监听,只有当当前的Watcher回调执行完毕了,才会向Server注册新的Watcher。
    • 轻量级:Client向Server发送的Watcher不是一个完整的,而是简易版。另外,回调逻辑不是Server端的,而是Client的。

    2、客户端命令

    (1)启动客户端

    ① 连接本机Zookeeper服务器

    ② 连接其他Zookeeper服务器

    算了,命令自己百度整理吧...

    3、ZKClient客户端

    4、Curator客户端

    五、Zookeeper典型应用场景

    1、配置维护

    (1)什么是配置维护

    在分布式系统中,很多服务都是部署在集群中的,即多台服务器中部署着完全相同的应用,起着完全相同的作用。当然,集群中的这些服务器的配置文件是完全相同的。

    若集群中服务器的配置文件需要进行修改,那么我们就需要逐台修改这些服务器中的配置文件。如果我们集群服务器比较少,那么这些修改还不是太麻烦,但如果集群服务器特别多,比如某些大型互联网公司的Hadoop集群有数千台服务器,那么纯手工的更改这些配置文件几乎就是一件不可能完成的任务。即使使用大量人力进行修改可行,但过多的人员参与,出错的概率大大提升,对于集群所形成的危险是很大的。

    (2)实现原理

    Zookeeper可以通过“发布/订阅模型”实现对集群配置文件的管理与维护。“发布/订阅模型”分为推模式(Push)与拉模式(Pull)。Zookeeper的“发布/订阅模型”采用的是推拉相结合的模式。 

     2、命令服务

    (1)什么是命令服务

    命名服务指可以为一定范围内的元素命名一个唯一标识,以与其它元素进行区分。在分布式系统中被命名的实体可以是集群中的主机、服务地址等。 

    (2)实现原理

    通过利用Zookeeper中顺序节点自动生成唯一编号的特点来实现命名服务。

    首先创建一组业务相关的节点,然后在这些节点下再创建顺序节点,此时的顺序节点的路径加名称即为生成的唯一标识。

    3、DNS服务

    Zookeeper的DNS服务是命名服务的一种特殊用法。其对外表现出的功能主要是防止提供者的单点问题,实现对提供者的负载均衡。(场景:Dubbo + Zookeeper)

    (1)什么是DNS

    DNS,Domain Name System,域名系统,即可以将一个名称与特定的主机IP加端口号进行绑定。Zookeeper可以充当DNS的作用,完成域名到主机的映射。 

    (2)基本DNS实现原理

    假设应用程序app1与app2分别用于提供service1和service2两种服务,现要将其注册到 Zookeeper中,具体的实现步骤如下图所示:

    (3)具有状态收集功能的DNS实现原理

    若某应用(例如app1)具有多个服务名称,则可以在该应用节点下添加多个子节点 。

    若某域名下的服务提供者增加,则首先可以读取该节点中的数据内容,然后再将该增加的提供者主机信息与原来数据内容一起再写入到节点数据内容中。

    若某域名需要修改,则可直接新增一个节点。

     

    4、Master选举

    (1)什么是Master选举

    集群是分布式系统中不可或缺的组成部分,是为了解决分布式系统中计算单元的单点问题,水平扩展计算单元的处理能力的一种解决方案。

    一般情况下,会在集群中选举出一个Master,用于协调集群中的其他Slave主机,对于Slave主机的状态具有决定权。

    例如,读写分离集群,Master处理写请求,Slave处理读请求(主从集群)。

    再如,对于复杂处理逻辑的系统,Master负责处理复杂逻辑计算,然后将计算结果写入到一个中间存储系统(DB或DFS等),Slave负责从中间存储系统中读取结果,并向读请求进行响应。 

    (2)广告推荐系统

    ① 需求

    系统会根据用户画像,将用户归结为不同的种类。系统会为不同种类的用户推荐不同的广告。每个用户前端需要从广告推荐系统中获取到不同的广告ID。

    ② 分析

    这个向前端提供服务的广告推荐系统一定是一个集群,这样可以更加快速高效的为前端进行响应。需要注意,推荐系统对于广告ID的计算是一个相对复杂且消耗CPU等资源的过程。如果让集群中每一台主机都可以执行这个计算逻辑的话,那么势必会形成资源浪费,且降低了响应效率。此时,可以只让其中的一台主机去处理计算逻辑,然后将计算的结果写入到某中间存储系统中,并通知集群中的其它主机从该中间存储系统中共享该计算结果。那么,这个运行计算逻辑的主机就是Master,而其它主机则为Slave。 

    ③ 架构

    ④ Master选举

    这个广告推荐系统集群中的Master是如何选举出来的呢?使用Zookeeper可以完成。使用Zookeeper中多个客户端对同一节点创建时,只有一个客户端可以成功的特性实现。

    由以下几步完成:

    1. 多个客户端同时发送对同一个临时节点进行创建的请求,最终只能有一个创建成功,这个创建成功的客户端就是Master,其他客户端就是Slave;
    2. 让所有Slave都向这个临时节点的父节点注册一个子节点列表的Watcher监听;
    3. 一旦该Master宕机,临时节点就会消失,Zookeeper服务器就会向所有Slave发送事件,Slave在接收到事件后会调用相应的回调,该回调会重新向这个父节点创建相应的临时子节点。谁创建成功,谁就是新的Master。

    (利用数据库的唯一主键也可以完成Master的选举,但Master宕机后,无法实现自动Master重选)

    5、分布式同步

    (1)什么是分布式同步

    分布式同步,也称分布式协调,是分布式系统中不可缺少的环节,是将不同的分布式组件有机结合起来的关键。对于一个在多台机器上运行的应用而言,通常需要一个协调者来控制整个系统的运行流程,例如执行的先后顺序,或执行与不执行等。 

    (2)MySQL数据复制总线

    ① 数据复制总线组成

    MySQL数据复制总线是一个实时数据复制框架,用于在不同的MySQL数据库实例间进行异步数据复制。其核心部分由三部分组成:生产者、复制管道、消费者。

    那么,MySQL数据复制总线系统中哪里需要使用Zookeeper的分布式同步功能呢?以上结构中可以显示看到存在的问题:Replicator存在单点问题。为了解决这个问题,就需要为其设置多个热备主机。那么,这些热备主机是如何协调工作的呢?这时候就需要使用Zookeeper来做协调工作了,即由Zookeeper来完成分布式同步工作。 

    ② 数据复制总线工作原理

    MySQL复制总线的工作步骤,总的来说分为三步。

    A、复制任务注册

    B、replicator热备

    C、主备切换

    6、集群管理

    (1)需求

    对于集群,我们总是希望能够随时获取到以下信息:

    • 当前集群中各个主机的运行时状态
    • 当前集群中主机的存活状况

    (2)基本原理

    (3)扩展

    该功能使用Agent也可以完成,与Agent不同的是,对于集群中主机的存活状态,使用Zookeeper可以做到实时监控。 

    (4)分布式日志收集系统

    ① 系统组成

    分布式日志收集系统由四部分组成:日志源集群、日志收集器集群、Zookeeper集群、 监控系统。

    ② 系统工作原理

    A、收集器的注册

    B、任务分配  

    C、状态收集

    D、任务再分配Rebalance 

    7、分布式锁

    分布式锁是控制分布式系统同步访问共享资源的一种方式。Zookeeper可以实现分布式锁功能。根据用户操作类型的不同,可以分为排他锁和共享锁。

    • 排他锁:写锁。
    • 共享锁:读锁。

    (1)分布式锁的实现

    在Zookeeper上对于分布式锁的实现,使用的是类似于“/xs_lock/[hostname]-请求类型-序号”的临时顺序节点。 

     

    其具体过实现过程如下:

    ① 当一个客户端向某资源发出读/写请求操作时,该客户端首先会对/xs_lock节点注册子节点列表变更事件的Watcher监听,随时监听子节点的变化情况;

    ② Watcher注册完毕后,其会在/xs_lock节点下创建一个读写操作的临时顺序节点。读写操作的顺序性就是通过这些子节点的顺序性体现的。注意,读写操作所创建的节点名称是不同的;

    ③ 节点创建完后,其就会触发客户端的Watcher回调,读取/xs_lock节点 下的所有子节点列表,然后比较当前子节点会与其它子节点序号的大小关系,并根据读写操作的不同,执行不同的逻辑;

    • 读请求:若没有比自己小或比自己小都是读请求的节点,则当前请求可以直接读取;若比自己小的节点中存在写请求节点,则当前请求等待。
    • 写请求:若没有比自己小的节点,则直接进行写操作。若发现存在比自己小的节点,则当前写操作都需要等待。

    ④ 客户端操作完毕后,与Zookeeper的连接断开,则Zookeeper中该会话对应的节点消失。

    (2)分布式锁的改进

    羊群效应:由于一个操作而引发了大量的低效或无用的操作的执行。

    前面的实现方式存在“羊群效应”,为了解决其所带来的性能下降,可以对前述分布式锁的实现进行改进。

    当客户端请求发出后,在Zookeeper中创建相应的临时顺序节点后马上获取当前的/xs_lock的所有子节点列表,但任何客户端都不向/xs_lock注册用于监听子节点列表变化的Watcher,而是改为根据请求类型的不同向“对其有影响的”子节点注册Watcher。

    • 读请求:只需要关注比其序号小的最后一个写节点。
    • 写请求:仅关注比自己小的最后一个节点,无论是读节点还是写节点。

    8、分布式队列

    说到分布式队列,马上可以想到RabbitMQ、Kafka等分布式消息队列中间件产品。Zookeeper也可以实现简单的分布式锁。

    (1) FIFO队列

    Zookeeper实现FIFO队列的思路:利用顺序节点的有序性,为每个数据在Zookeeper中创建一个相应的节点,然后为每个节点都注册Watcher监听。 一个节点被消费,则会引发消费者消费下一个节点,直到消费完毕。

    (2)分布式屏障Barrier队列

     

    Barrier,屏障、障碍物。Barrier队列是分布式系统中的一种同步协调器,规定了一个队列中的元素必须全部聚齐后才能继续执行后面的任务,否则一直等待。其常见于大规模分布式并行计算的应用场景中:最终的合并计算需要基于很多并行计算的子结果来进行。

    Zookeeper对于Barrier的实现原理是,在Zookeeper中创建一个/barrier节点,其数据内容设置为屏障打开的阈值,即当其下的子节点数量达到阈值后,app才可进行最终的计算,否则一直等待。每一个并行运算完成,都会在/barrier下创建一个子节点,直到所有并行运算完成。

  • 相关阅读:
    依赖属性
    浅拷贝与深拷贝
    使用Advanced Installer打包工具如何设置是否安装预安装程序包
    WPF布局容器
    找不到UseInMemoryDatabase方法
    从零开始学.net core(一)
    那些年我们改过的规则代码
    办公达人私藏的EXCEL辅助工具,一人抵十人,高效办公就靠它了!
    面试题:整理
    面试: Vue数组的变异方法
  • 原文地址:https://www.cnblogs.com/ZekiChen/p/12856455.html
Copyright © 2011-2022 走看看