1.概述

Zookeeper是开源的分布式应用协调系统。布式应用程序可以基于 Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应,从而实现集群中类似Master/Slave管理模式。
Zookeeper=文件系统+通知机制

场景

数据发布/订阅(配置管理)

发布与订阅即所谓的配置管理,将数据发布到ZooKeeper节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,地址列表等非常适合。
基于Zookeeper的实现方式

  1. 数据存储:将数据(配置信息)存储到Zookeeper上的一个数据节点
  2. 数据获取:应用在启动初始化节点从Zookeeper数据节点读取数据,并在该节点上注册一个数据变更Watcher
  3. 数据变更:当变更数据时,更新Zookeeper对应节点数据,Zookeeper会将数据变更通知发到各客户端,客户端接到通知后重新读取变更后的数据即可。

负载均衡
集群管理
是否有机器退出和加入、选举master。对于机器的退出,所有机器约定在父目录下创建临时目录,对于新机器的加入,所有机器创建临时顺序编号目录节点。

分布式锁

两种方式实现基于ZooKeeper的分布式锁:

  • 节点名称唯一性:多个客户端创建一个节点,只有成功创建节点的客户端才能获得锁
  • 临时顺序节点:所有客户端在某个目录下创建自己的临时顺序节点,只有序号最小的才获得锁

    队列管理

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

  • 一个job由多个task组成,只有所有任务完成后,job才运行完成。

  • 为job创建一个/job目录,然后在该目录下,为每个完成的task创建一个临时Znode,一旦临时节点数目达到task总数,则表明job运行完成。

队列按照FIFO方式进行入队和出队操作,例如实现生产者和消费者模型。
Leader选举本质上是一个分布式锁,有
一种非常常用的选举leader的方式是“Majority Vote”(“少数服从多数”),但Kafka并未采用这种方式。这种模式下,如果我们有2f+1个Replica(包含Leader和Follower),那在commit之前必须保证有f+1个Replica复制完消息,为了保证正确选出新的Leader,fail的Replica不能超过f个。因为在剩下的任意f+1个Replica里,至少有一个Replica包含有最新的所有消息。这种方式有个很大的优势,系统的latency只取决于最快的几个Broker,而非最慢那个。Majority Vote也有一些劣势,为了保证Leader Election的正常进行,它所能容忍的fail的follower个数比较少。如果要容忍1个follower挂掉,必须要有3个以上的Replica,如果要容忍2个Follower挂掉,必须要有5个以上的Replica。也就是说,在生产环境下为了保证较高的容错程度,必须要有大量的Replica,而大量的Replica又会在大数据量下导致性能的急剧下降。这就是这种算法更多用在ZooKeeper这种共享集群配置的系统中而很少在需要存储大量数据的系统中使用的原因
同一时刻多台机器创建同一个节点,只有一个会争抢成功。利用这个特性可以做分布式锁。
临时节点的生命周期与会话一致,会话关闭则临时节点删除。这个特性经常用来做心跳,动态监控,负载等动作
顺序节点保证节点名全局唯一。这个特性可以用来生成分布式环境下的全局自增长id
zxid
有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)
ZooKeeper状态的每一次改变, 都对应着一个递增的Transaction id, 该id称为zxid. 由于zxid的递增性质, 如果zxid1小于zxid2, 那么zxid1肯定先于zxid2发生.
创建任意节点, 或者更新任意节点的数据, 或者删除任意节点, 都会导致Zookeeper状态发生改变, 从而导致zxid的值增加.

角色

Leader

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

    Follower

  • 处理客户端的非事务请求,转发事务请求给Leader服务器

  • 参与事务请求Proposal的投票
  • 参与Leader选举投票

    Observer

    3.3.0版本以后引入的一个服务器角色,在不影响集群事务处理能力的基础上提升集群的非事务处理能力

  • 处理客户端的非事务请求,转发事务请求给Leader服务器

  • 不参与任何形式的投票

    选举算法

    首先一般选举里面都有2N+1台集器,如果是三台机器的A、B、C,A和B都会选举自己,每次投票会包括推举的服务器的myid和zxid,使用(myid,zxid)来表示。
    举主要是两种场景:初始化、leader不可用。
    集群中某一台机器启动比较晚,在它启动之前,集群已经正常工作,即已经存在一台leader服务器。当该机器试图去选举leader时,会被告知当前服务器的leader信息,它仅仅需要和leader机器建立连接,并进行状态同步即可。
    leader不可用,
    (1)初始阶段,都会给自己投票。
    (2)当接收到来自其他服务器的投票时,都需要将别人的投票和自己的投票进行pk,规则如下:
    优先检查zxid。zxid比较大的服务器优先作为leader。如果zxid相同的话,就比较sid,sid比较大的服务器作为leader。

    工作原理

    Zookeeper 的核心是原子广播机制,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。

    zab协议

    Zab(Zookeeper Atomic Broadcast)协议有两种模式,它们 分别是恢复模式(选主)和广播模式(同步)。
    恢复模式
    当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。leader选举是保证分布式数据一致性的关键。
    广播模式
    一旦 leader 已经和多数 follower 进行了状态同步后,可以开始广播消息即进入广播状态。当一个 server 加入 ZooKeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。ZooKeeper 服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的 followers 支持。

    数据一致性

    Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。

    Paxos

    节点

    ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M

  • 持久节点(PERSISTENT):除非手动删除,否则节点一直存在于Zookeeper上。

  • 临时节点(EPHEMERAL):临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与zookeeper连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。
  • 持久顺序节点(PERSISTENT_SEQUENTIAL):基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。
  • 临时顺序节点(EPHEMERAL_SEQUENTIAL):基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。

    zookeeper集群一般为奇数?

    •Leader选举算法采用了Paxos协议; •Paxos核心思想:当多数Server写成功,则任务数据写成功。如果有3个Server,则两个写成功即可;如果有4或5个Server,则三个写成功即可。 •Server数目一般为奇数(3、5、7)如果有3个Server,则最多允许1个Server挂掉;如果有4个Server,则同样最多允许1个Server挂掉由此,
    我们看出3台服务器和4台服务器的的容灾能力是一样的,所以为了节省服务器资源,一般我们采用奇数个数,作为服务器部署个数。

    Watch

    client端会对某个znode 注册一个watcher事件,当该znode发生变化时,这些client会收到ZooKeeper的通知,然后client可以根据znode变化来做出业务上的改变等。
    zookeeper的服务注册与发现,主要应用的是zookeeper的znode节点数据模型和watcher机制,大致的流程如下:
    image.png
    服务注册:服务提供者(Provider)启动时,会向zookeeper服务端注册服务信息,也就是创建一个节点,例如:用户注册服务com.xxx.user.register,并在节点上存储服务的相关数据(如服务提供者的ip地址、端口等)。
    服务发现:服务消费者(Consumer)启动时,根据自身配置的依赖服务信息,向zookeeper服务端获取注册的服务信息并设置watch监听,获取到注册的服务信息之后,将服务提供者的信息缓存在本地,并进行服务的调用。
    服务通知:一旦服务提供者因某种原因宕机不再提供服务之后,客户端与zookeeper服务端断开连接,zookeeper服务端上服务提供者对应服务节点会被删除(例如:用户注册服务com.xxx.user.register),随后zookeeper服务端会异步向所有消费用户注册服务com.xxx.user.register,且设置了watch监听的服务消费者发出节点被删除的通知,消费者根据收到的通知拉取最新服务列表,更新本地缓存的服务列表。
    znode节点可以设置两类watch,一种是DataWatches,基于znode节点的数据变更从而触发 watch 事件,触发条件getData()、exists()、setData()、 create()。
    另一种是Child Watches,基于znode的孩子节点发生变更触发的watch事件,触发条件 getChildren()、 create()。

    Zookeeper 和 Dubbo