分布式协调服务ZooKeeper(二)

6. 分布式协调

在这里插入图片描述

6.1 扩展性

在这里插入图片描述

在这里插入图片描述

ZooKeeper具有很好的扩展性,主要体现在其架构中设计的角色除了Leader和Follower之外,还有一个只能提供查询服务的Observer的角色,它可以在提供读写分离服务时,去放大查询能力。

zk集群实际生产使用时,可以使用5台或7台,相对少些的Follower,配置绝大多数的Observer提供读服务,这样既可以进行快速的投票选举,又能更好的对外提供读服务。

集群配置时,比较简单,我们人工做好规划后,在zk的配置文件zoo.cfg中添加对Observer的配置即可:

server.1=node01:2888:3888
server.2=node02:2888:3888
server.3=node03:2888:3888
server.4=node04:2888:3888:observer  # 增加Observer

6.2 可靠性

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

学好ZooKeeper记住这最重要的一句话就可以:攘其外必先安其内。

1、安其内:对内选主(快速恢复Leader)。

2、攘其外:对外可用(数据可靠/可用/一致性)。

ZooKeeper的可靠性源自其特有的快速恢复Leader机制和**数据可靠(一致性)**的支撑。

快速恢复Leader的内容,我会在下文单独的【集群选举】中去介绍。

这里着重介绍zk如何保证数据可靠或者说数据最终一致性,其依赖于分布式中一个基于消息传递的一致性算法Paxos和zk的原子广播协议ZAB

6.2.1 Paxos

在ZK Server最基础的东西是什么呢?应该就是Paxos了。

那么什么是Paxos呢?它是一个基于消息传递的一致性算法,Leslie Lamport在1990年提出,后来被广泛应用于分布式计算中,Google的Chubby,Apache的ZooKeeper都是基于它的理论来实现的,Paxos还被认为是到目前为止唯一的分布式一致性算法,其他的算法都是Paxos的改进或简化。

具体内容,可参见如下文章。

在百度上搜索曾经看过的域名,
可这么搜索:paxos  site:douban.com,
这样搜索的域名,都是豆瓣的

==》搜索到参考文章:https://www.douban.com/note/208430424/

6.2.2 ZAB

在解决分布式一致性方面,ZooKeeper并没有使用Paxos,而是采用了ZAB协议,相当于Paxos的精简版。

ZAB(ZooKeeper Atomic Broadcast)即ZooKeeper原子广播协议,是为分布式协调服务ZK专门设计的一种支持奔溃恢复原子广播协议。

基于该协议,ZK实现了一种主从模式的系统架构来保持集群中各个节点之间的数据一致性,其要点如下:

  1. 必须作用在可用状态,即有Leader的情况;

  2. 原子:要么成功,要么失败,没有中间状态(队列+2PC);

    队列:FIFO,顺序性。

    2PC:两阶段提交:

    第一阶段:写日志,到磁盘;

    第二阶段:write数据,到内存。

  3. 广播:分布式多节点的,不需要全部知道,过半即可;

  4. ZK的数据状态在内存,日志保存在磁盘。

下面是在ZooKeeper中执行ZAB原子广播的流程。

在这里插入图片描述

如上图所示:

  1. 客户端Client和ZooKeeper集群中的某一个Follower建立连接后,发送一条create创建节点的写指令;

  2. Follower接收到create指令后,将其转发至集群中的Leader;

  3. Leader成功接收到create指令,首先生成一个事务ID–Zxid;

  4. Leader内部为每一个Follower维护着一个队列,通过这些队列分别向各个Follower同步数据,即刚才的create指令,此阶段类似于2PC

    (1)4-1. Leader首先向各个Follower发送写日志的请求,等待Follower的ACK确认,这里只要求半数以上的Follower返回OK即可;

    (2)4-2:当有半数以上的Follower写日志成功后,Leader再向各个Follower发送写数据请求,即将之前的create指令执行,同样要求半数以上。

  5. 当上述步骤完成后,Leader再将create指令成功执行的ACK确认结果,再通过先前的Follower给Client客户端返回OK。

整体过程中,ZooKeeper就是通过队列+2PC达到数据快速同步,保证了其数据的可靠性和一致性。

6.3 集群选举

在这里插入图片描述

6.3.1 两个场景

集群选举Leader要分为两个场景进行讨论:

  1. 第一次启动集群;
  2. 重启集群,或者Leader挂了之后。

6.3.2 集群节点Node的ID属性

集群中每个节点Node都有自己的两个ID:

  1. Myid:配置集群时,每个服务器节点都要定义自己的Myid。
  2. Zxid:事务ID。

6.3.3 成为新的Leader的要求

  1. 数据最新最全的Zxid;

    在ZK集群中使用过半机制,所以必须是过半通过的数据才是真数据,即我们见到的可用的Zxid,这是前提。

  2. 在Zxid相同的情况下,选择Myid值最大的;

6.3.4 选举过程

ZooKeeper集群中的Leader选举过程是简单+快速的:

  1. 各个节点通过3888端口进行两两通信;
  2. 只要任何人投票,都会触发那个准Leader发起自己的投票;
  3. 推举制:先比较Zxid,如果相同,再比较Myid。

选举过程要分两种情形讨论:

第一种情况:第一次启动集群。

由于此时各个节点为初始状态,它们的Zxid都为0,完全相同,所以只需推举Myid值最大的即可。同时因为有过半机制,所以启动的Server台数过半即可选出Leader。

例如现有Node01~node04共计4台ZK Server,只需要启动3台后即可选出Leader,不论以何种顺序启动,都是推举Myid值最大的Node即可。

第二种情况:重启集群,或者Leader挂了之后。

这种情况由于集群在运行时已经产生了Zxid,所以选举过程相对复杂一点,下面针对下图说明。

在这里插入图片描述

如上图所示,ZooKeeper集群中共有node01~node04这4台Server,原来的Leader是node04,当它挂了之后,就需要在余下3台Follower中快速选举出一个新的Leader:

  1. 此时的情形是node03中的Zxid=7&Myid=3,而node01的Zxid=8&Myid=1,node02的Zxid=8&Myid=2;
  2. 首先node03会先投自己1票【并记录:node03+1】带着自己的Zxid=7向node01和node02发送信息,为自己投票;
  3. node03发出的信息首先到达node01,通过比较发现node03的Zxid比自己的小,便拒绝node03的请求,并为自己投上1票【并记录:node01+1】,同时将node01和node03发送信息;
  4. 当node02接收到node03的请求后,同样因为Zxid小于自己而拒绝node03,并且为自己投上1票【并记录:node02+1】,同时向其他两台发出信息;
  5. 之后由于node01和node02的Zxid相同,就需要继续比较Myid,当node01发现node02的Myid大于自己时,就会为node02投票【并记录:node02+1】,再发送信息到node02,此时node02上【会记录:node02+2】;
  6. 同理node03也会为node02投票,这样在node02上会最后【记录:node02+3】。最后每个Node都会记录node02的3票,node02被选举为新的Leader。

通过以上过程,我们会发现,在ZooKeeper集群重新选举Leader,与其说选举,不如说是推举制,即其他node发现准Leader在为自己投票后,便会一直推举其为新的Leader。这样既达到快速恢复Leader的目的,同时保证了数据的最前最新–可靠性。

6.4 Watch

在这里插入图片描述

Zookeeper中支持Watch(监控、观察、回调)的概念。客户端可以在znode上设置监控。当znode发生变化时,watch将被触发并移除。当触发监控时,客户端会收到一个数据包,表明znode已更改。如果客户端和其中一台ZooKeeper服务器之间的连接断开,客户端将收到本地通知。

在ZooKeeper3.6.0中的新增功能:客户端还可以在znode上设置永久的递归监视,这些监视在触发时不会被删除,并且递归地触发已注册znode以及任何子znode上的更改。

在这里插入图片描述

如上图所示,我们来阐述一下ZooKeeper中watch机制:

  1. 客户端client1和ZooKeeper集群的某一台Server建立连接,在Server中创建了一个临时节点(/ooxx/a);
  2. 客户端client2连接ZK,可以通过get /ooxx/a访问到该目录下的data数据,并且每次访问时都想知道该节点是否仍然存在,其中的数据有无修改等;
  3. 此时监控的方式,可以自己实现一个心跳机制,没隔多久去验证一下client1的情况,但是这种方式时效性差,可能一次心跳验证刚完毕,client就断开连接或者修改数据,心跳间隔期间client2就不会及时发现;
  4. 而ZooKeeper给我们提供了一个Watch机制,即通过watch /ooxx/a的方式,当其中的数据发生create/delete/change/children时会产生event事件,ZK的node会发送一个回调callback给client2。这种方式既不用client2主动实现监控,又具有很好的时效性,能在client1产生数据变动后立刻得到通知。

在ZooKeeper中的Watch有两类:

  1. 第一类:new zk时,传入的watch,这个watch是session级别的,跟path,node没有关系。

  2. 第二类:监控其他客户端的watch,跟path有关,是一次性的,每次调用需要重新watch。

    注:在ZooKeeper3.6.0中有新增变化,上文提到过

6.5 API

在这里插入图片描述

ZooKeeper的设计目标之一是提供一个非常简单的编程接口(简单的API)。因此,它仅支持以下操作:

  • create:在目录树中的某个位置创建一个节点。
  • delete:删除一个节点。
  • exists:测试某个位置是否存在节点。
  • get date:从节点读取数据。
  • set data:将数据写入节点。
  • get children:检索节点的子节点列表。
  • sync:等待时间传播,即同步数据。

在使用ZooKeeper的API时,注意以下几点:

  1. 使用ZK编程时,集群和客户单版本号必须相同。

  2. ZK是有session概念的,没有连接池概念。

  3. watch的调用,只会发生在读调用,如get/exists等方法。

  4. ZK可以通过代码实现分布式配置。

    ZooKeeper最大的优势,是它的异步回调机制

    Callback -> reactive,响应式编程,更充分压榨OS,HW资源、性能。

    如下图所示,分布式系统中,可以通过ZooKeeper的Watch机制去管理分布式配置数据data:

    • client watch [zk 配置数据data];
    • client get [zk 配置数据data];
    • 其他的client修改配置数据 set [zk 配置数据data]。

在这里插入图片描述

6.6 分布式锁

在这里插入图片描述

在介绍Redis时,我们提到过实现分布式锁的更好的方案是通过ZooKeeper,基于它的临时节点,序列和Watch监控机制。下问会详细说明实现原理。

在这里插入图片描述

如上图所示,多个客户端要访问同一个资源(临界资源),当发生写数据时就必然涉及到多线程下的数据安全问题,分布式情况下就需要用分布式锁来实现。而ZooKeeper由于它优秀的机制非常适合用来做分布式锁,其原理如下:

  1. 首先是多个客户端都连接到ZooKeeper Server,它们同时去争抢锁,但只有一个人能获得锁,之后才能去访问并设置资源数据;

  2. 这时有一个问题,如果获得锁的人出现问题,就会导致因无法释放锁,同时其他人又无法抢到锁,即出现死锁的情况。那么ZK是如何规避死锁呢?使用临时节点(ephemeral node),因为它会随着session会话的断开而消亡;

  3. 获得锁的人成功了,释放锁;

  4. 这时又有一个问题:锁被释放、删除,别人怎么知道的?这里有以下几种情况:

    (1)主动轮询:其他的人(客户端)通过主动轮询–心跳机制,每隔多久去检测锁的情况。显然这种方式存在很大的弊端,就是延迟和压力;

    (2)通过ZooKeeper的Watch去监控,这种方式可以解决延迟问题,但是压力问题仍然存在。比如有100个客户端,那么得到锁的客户端一旦释放锁,就需要给另外99个发事件回调,成本非常高;

    (3)通过序列sequence+watch,此时需要watch谁呢?watch前一个即可。我们将所有的临时序列节点进行排序,最小的获得锁,一旦它释放锁后,只需要给第二个发送事件回调就可以了,成本非常小。

ZooKeeper Server,它们同时去争抢锁,但只有一个人能获得锁,之后才能去访问并设置资源数据;

  1. 这时有一个问题,如果获得锁的人出现问题,就会导致因无法释放锁,同时其他人又无法抢到锁,即出现死锁的情况。那么ZK是如何规避死锁呢?使用临时节点(ephemeral node),因为它会随着session会话的断开而消亡;

  2. 获得锁的人成功了,释放锁;

  3. 这时又有一个问题:锁被释放、删除,别人怎么知道的?这里有以下几种情况:

    (1)主动轮询:其他的人(客户端)通过主动轮询–心跳机制,每隔多久去检测锁的情况。显然这种方式存在很大的弊端,就是延迟和压力;

    (2)通过ZooKeeper的Watch去监控,这种方式可以解决延迟问题,但是压力问题仍然存在。比如有100个客户端,那么得到锁的客户端一旦释放锁,就需要给另外99个发事件回调,成本非常高;

    (3)通过序列sequence+watch,此时需要watch谁呢?watch前一个即可。我们将所有的临时序列节点进行排序,最小的获得锁,一旦它释放锁后,只需要给第二个发送事件回调就可以了,成本非常小。

对于ZooKeeper Server的内容其实很多,本文只是做了一些基本的介绍。ZooKeeper是一个非常优秀的分布式应用协调服务,关于其API的使用(响应式编程方式),以及分布式锁的实现代码,后期再整理归纳。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值