ZooKeeper解读

简介

Zookeeper 是一个分布式服务协调框架,可以用来维护分布式配置中心,服务注册中心,实现分布式锁等。在 Hbase、Hadoop、kafka 等项目中都有广泛的应用

概念解释:Zookeeper 是一个分布式协调服务,就是为用户的分布式应用程序提供协调服务的框架

  • zookeeper 是为别的分布式程序服务的
  • zookeeper 本身就是一个分布式程序(只要有半数以上节点存活,它就能正常服务)
  • zookeeper 所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理、分布式共享锁、统一名称服务等
  • 虽然说可以提供各种服务,但是 zookeeper 在底层其实只提供了两个功能:1.管理(存储,读取)用户程序提交的数据 2.并为用户程序提供数据节点监听服务

zookeeper 节点类型

每个节点在 zookeeper 中叫做 znode,并且其有一个唯一的路径标识

Zookeeper 是用类似资源文件目录的方式来管理节点,每个节点可以存储数据。ZooKeeper 有四种不同类型的节点:

  • PERSISTENT:持久化节点,一旦创建后,即使客户端与 zookeeper 断开了连接,该节点依然存在
  • PERSISTENT_SEQUENTIAL:持久化顺序节点,一旦创建后,即使客户端与 zookeeper 断开了连接,该节点依然存在,并且节点自动按照顺序编号
  • EPHEMERAL:临时节点,当客户端与 zookeeper 断开连接之后,该节点就被删除
  • EPHEMERAL_SEQUENTIAL:临时顺序节点,当客户端与 zookeeper 断开连接之后,该节点就被删除,并且节点自动按照顺序编号

zookeeper 的每一个节点,都是一个天然的顺序发号器。在每一个节点下面创建子节点时,只要选择的节点类型是顺序的(持久化顺序节点或临时顺序节点)类型,那么,在新的子节点后面会加上一个次序编号,这个次序编号,是上一个生成的次序编号加 1

在这里插入图片描述

zookeeper 会话 session

  • 一个客户端连接一个会话,由 zookeeper 分配唯一会话 id
  • 客户端以特定的时间间隔发送心跳以保持会话有效,即配置文件 zoo.cfg 的配置参数 tickTime
  • 超过会话超时时间未收到客户端的心跳,则判定客户端服务器宕机。超时时间默认为 2 倍的 tickTime
  • 会话中的请求按 FIFO(先进先出) 顺序执行

zookeeper 特性

  • 全局数据一致:集群中每个服务器保存一份相同的数据副本,client 无论连接到哪个服务器,展示的数据都是一致的,这是最重要的特征
  • 可靠性:如果消息被其中一台服务器接受,那么将被所有的服务器接受
  • 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息 a 在消息 b 前发布,则在所有 Server 上消息 a 都将在消息 b 前被发布;偏序是指如果一个消息 b 在消息 a 后被同一个发送者发布,a 必将排在 b 前面
  • 数据更新原子性:一次数据更新要么成功(半数以上节点成功),要么失败,不存在中间状态
  • 实时性:Zookeeper 保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息

zookeeper 集群角色

在这里插入图片描述

  • Leader:它是 zookeeper 集群工作的核心。事务请求(写数据)的唯一调度者,保证集群事务处理的顺序性。它是集群内部各个服务器的调度者。事务:对于 create,setData,delete 等有写操作的请求,则需要统一转发给 leader 处理,leader 需要决定编号、执行操作,这个过程称为一个事务
  • Follower:处理客户端非事务(读操作)请求,转发事务请求给 Leader; 参与集群 Leader 选举投票
  • 此外,针对访问量比较大的 zookeeper 集群,还可新增观察者角色。 Observer:观察者角色,观察 Zookeeper 集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给 Leader 服务器进行处理。不会参与任何形式的投票只提供非事务服务,通常用于在不影响集群事务 处理能力的前提下提升集群的非事务处理能力

Zookeeper Watcher

zookeeper 提供了分布式数据发布/订阅功能,一个典型的发布/订阅模型系统定义了一种一对多的订阅关系。能让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态变化时,会通知所有的订阅者,使他们能做出相应的处理

zookeeper 中,引入了 Watcher 机制来实现这种分布式的通知功能。zookeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些事件触发了这个 Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能

触发事件种类很多,如节点的创建,节点的删除,节点的改变,子节点改变等

总的来说,可以概括 Watcher 的实现由一下过程,

  1. 客户端向服务端注册 Watcher
  2. 服务端事件触发 Watcher
  3. 客户端回调 Watcher 得到事件触发情况

Watch 机制特点

  • 一次性触发:事件发生触发监听,一个 watcher event 就会被发送到设置监听的客户端, 这种效果是一次性的,后续再次发生同样的事件,不会再次触发
  • 事件封装:ZooKeeper 使用 WatchedEvent 对象来封装服务端事件并传递。 WatchedEvent 包含了每一个事件的三个基本属性: 通知状态(keeperState),事件类型(EventType)和节点路径(path
  • event 异步发送:watcher 的通知事件从服务端发送到客户端是异步的
  • 先注册再触发:Zookeeper 中的 watch 机制,必须客户端先去服务端注册监听,这样事件发 送才会触发监听,通知给客户端

Zookeeper Watcher 示例

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>    
    <version>3.4.9</version>
</dependency>
public static void main(String[] args) throws Exception {
    
     
    // 初始化 ZooKeeper 实例(zk 地址、会话超时时间,与系统默认一致、watcher) 
	ZooKeeper zk = new ZooKeeper("node-1:2181,node-2:2181", 30000, new Watcher() {
    
     
    
    	@Override 
    	public void process(WatchedEvent event) {
    
    
         	System.out.println("事件类型为:" + event.getType()); 
         	System.out.println("事件发生的路径:" + event.getPath()); 
         	System.out.println("通知状态为:" +event.getState()); 
     	} 
 	}); 
 	
	zk.create("/myGirls", "性感的".getBytes("UTF-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 
	zk.close();
}
public static void main(String[] args) throws Exception {
    
     
	// 初始化 ZooKeeper 实例(zk 地址、会话超时时间,与系统默认一致、watcher) 
    ZooKeeper zk = new ZooKeeper("node-21:2181,node-22:2181", 30000, new Watcher() {
    
     
        
		@Override 
        public void process(WatchedEvent event) {
    
     
        	System.out.println("事件类型为:" + event.getType()); 
        	System.out.println("事件发生的路径:" + event.getPath()); 
        	System.out.println("通知状态为:" +event.getState()); 
     	} 
     }); 
     
	// 创建一个目录节点 
	zk.create("/testRootPath", "testRootData".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 
    // 创建一个子目录节点 
    zk.create("/testRootPath/testChildPathOne", "testChildDataOne".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 
    System.out.println(new String(zk.getData("/testRootPath",false,null))); 
    // 取出子目录节点列表 
    System.out.println(zk.getChildren("/testRootPath",true)); 
    // 修改子目录节点数据 
    zk.setData("/testRootPath/testChildPathOne","modifyChildDataOne".getBytes(),-1); 
    System.out.println("目录节点状态:["+zk.exists("/testRootPath",true)+"]"); 
    // 创建另外一个子目录节点 
    zk.create("/testRootPath/testChildPathTwo", "testChildDataTwo".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 
    System.out.println(new String(zk.getData("/testRootPath/testChildPathTwo",true,null))); 
    // 删除子目录节点 
    zk.delete("/testRootPath/testChildPathTwo",-1); 
    zk.delete("/testRootPath/testChildPathOne",-1); 
    // 删除父目录节点 
    zk.delete("/testRootPath",-1); zk.close(); 
}

ZooKeeper 选举机制

zookeeper 默认的算法是 FastLeaderElection采用投票数大于半数则胜出的逻辑

  • 服务器的 myId :比如有三台服务器,编号分别是 1,2,3。 编号越大在选择算法中的权重越大
  • 选举状态:Looking 是竞选状态。 Following 是随从状态,同步 leader 状态,参与投票。 Observing 是观察状态,同步 leader 状态,不参与投票。 Leading 是领导者状态
  • 数据事务的 zxId:服务器中存放的最新数据版本。 值越大说明数据越新,在选举算法中数据越新权重越大
  • 逻辑时钟:也叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比, 根据不同的值做出不同的判断

全新集群选举机制(新的服务器集群刚开始启动时进行选举)

假设目前有 5 台服务器,每台服务器均没有数据,它们的编号分别是 1,2,3,4,5。按编号依次启动,它们的选择举过程如下:

  1. 服务器 1 启动,给自己投票,然后发投票信息。由于其它机器还没有启动所以它收不到反馈信息,服务器 1 的状态一直处于 Looking(领导者状态)
  2. 服务器 2 启动,给自己投票,同时与之前启动的服务器 1 交换结果,由于服务器 2 的编号大,所以服务器 2 胜出。但此时投票数没有大于半数,所以两个服务器的状态依然是 Looking(领导者状态)
  3. 服务器 3 启动,给自己投票,同时与之前启动的服务器 1,2 交换结果,由于服务器 3 的编号大,所以服务器 3 胜出。此时投票数正好大于半数,所以服务器 3 成为领导者,服务器 1,2 成为小弟
  4. 服务器 4 启动,给自己投票,同时与之前启动的服务器 1,2,3 交换信息, 尽管服务器 4 的编号大,但之前服务器 3 已经胜出,所以服务器 4 只能成为小弟
  5. 服务器 5 启动,后面的逻辑同服务器 4 成为小弟

总结: 在此种情况下,由于服务器集群刚开始启动,所以是没有数据事务 zxId 的。所以此时的选举机制主要是依据服务器的 myId 来进行的,服务器的 myId 越大,它的权重越大

非全新集群选举机制(该服务器集群已经提供服务一段时间之后,由于某些原因,此时要重新进行选举)

对于运行正常的 zookeeper 集群,中途有机器宕掉,需要重新选举时,选举过程就需要加入数据 ID、服务器 ID 和逻辑时钟

  • 数据事务的 zxId:数据新的 version 越大,数据每次更新都会更新 versionzxId 越大说明数据越新
  • 服务器的 myId:就是我们配置的 myid 中的值,每个机器一个

逻辑时钟:这个值从 0 开始递增,每次选举对应一个值。 如果在同一次选举中,这个值是一致的,这样选举的标准就变成:

  • 逻辑时钟小的选举结果被忽略,重新投
  • 统一逻辑时钟后,数据事务的 zxId 大的胜出
  • 数据事务的 zxId 相同的情况下,服务器的 myid 大的胜出

根据这个规则选出 Leader

会发生 zookeeper 选举机制的情况

  • 集群服务器的开始启动
  • Leader 服务器宕掉
  • Follower 服务器宕掉,Leader 服务器发现已经没有过半的 Follower 服务器跟随自己了

ZooKeeper 典型应用场景

  • 数据发布与订阅(分布式配置中心):发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到 ZK 节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。应用在启动的时候会主动来获取一次配置,同时,在节点上注册一个 Watcher, 这样一来,以后每次配置有更新的时候,都会实时通知到订阅的客户端,从来达到获取最新配置信息的目的
  • 命名服务:在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等——这些我们都可以统称他们为名字(Name)。其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用 ZK 提供的创建节点的 API,能够很容易创建一个全局唯一的 path,这个 path 就可以作为 一个名称。阿里开源的分布式服务框架 Dubbo 中使用 ZooKeeper 来作为其命名服务,维护全局的服务地址列表
  • 分布式锁:分布式锁,这个主要得益于 ZooKeeper 保证了数据的强一致性

zookeeper 数据的一致性

zookeeper 集群中各个角色的职责:

  • Leader:负责处理所有请求,为客户端提供数据的读取和写入的角色
  • Follower:只提供读服务,有机会通过选举成为 Leader
  • Observer:只提供读服务

由以上三种角色的介绍可知,zookeeper 中的所有请求都是交个 Leader 角色来处理的。因此,如果 Leader 服务宕机了,zookeeper 就无法再提供服务了,这就是单点的弊端。而在 zookeeper 集群中,如果 Leader 服务宕机了,那么 Follower 角色能够通过选举成为新的 Leader 角色。那么问题来了,Follower 是如何被选举成为新的 Leader 的? 新的 Leader 又是如何保证数据的一致性的? 这些问题的答案都在于 zookeeper 所用的分布式一致性协议 ZAB

分布式一致性协议 ZAB(原子消息广播协议,保证消息的有序性)

事务概念:zookeeper 作为一个分布式协调服务,需要 Leader 节点去接受外部请求,转化为内部操作(比如创建,修改,删除节点),需要原子性执行的几个操作就组成了事务,这里用 T 代表。zookeeper 要求有序的处理事务,因此给每个事务一个编号,每进来一个事务编号加 1,假设 Leader 节点中进来 n 个事务,可以表示为 T1,T2,T3…Tn。为了防止单点问题导致事务数据丢失,Leader 节点会把事务数据同步到 Follower节点中

事务队列概念:LeaderFollower 都会保存一个事务队列,用 L 表示,L=T1,T2,T3…TnLeader 的事务队列是接受请求保存下来的,Follower 的事务队列是 Leader 同步过来的,因此 Leader 的事务队列是最新,最全的

任期概念:在 zookeeper 的工作过程中,Leader 节点奔溃,重新选举出新的 Leader 是很正常的事情,所以 zookeeper 的运行历史中会产生多个 Leader,就好比一个国家的历史中会相继出现多为领导人。为了区分各个 LeaderZAB 协议用一个整数来表示任期,我们假设用 E 表示任务。zookeeper 刚运行时选举出的第一个 Leader 的任期为 E=1;第一个 Leader 奔溃后,下面选举出来的 Leader,任期会加 1E=2 依次类推。加入任期概念的事务应该表示为 T(E,n)

ZAB 协议是为 zookeeper 专门设计的一种支持奔溃恢复的原子广播协议。虽然它不像 Paxos 算法那样通用,但是它却比 Paxos 算法易于理解。

ZAB 协议主要的作用在于三个方面

  • 选举出 Leader
    第一:每个 Follower 广播自己事务队列中最大事务编号 maxId
    第二:获取集群中其他 Follower 发出来的 maxId,选出最大的 maxId 所属的 Follower,投票给 Follower,选它为 Leader
    第三:统计所有投票,获取投票数超过一半的 Follower 被推选为 Leader
  • 同步节点之间的状态达到数据一致
    第一:各个 FollowerLeader 发送自己保存的任期 E
    第二:Leader 比较所有的任期,选取最大的 E,加 1 后作为当前的任期 E = E + 1
    第三:将任期 E 广播给所有 Follower
    第四:Follower 将任期改为 Leader 发过来的值,并且返回给 Leader 事务队列 L
    第五:Leader 从队列集合中选取任期最大的队列,如果有多个队列任期都是最大,则选取事务编号 n 最大的队列 Lmax。将 Lmax 设置为 Leader 队列,并且广播给各个 Follower
    第六:Follower 接收队列替换自己的事务队列,并且执行提交队列中的事务
    至此各个节点的数据达成一致,zookeeper 恢复正常服务
  • 数据的广播
    第一:Leader 节点接收到请求,将事务加入事务队列,并且将事务广播给各个 Follower
    第二:Follower 接收事务并加入到事务队列,然后给 Leader 发送准备提交请求
    第三:Leader 接收到半数以上的准备提交请求后,提交事务同时向 Follower 发送提交事务请求
    第四:Follower 提交事务

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

zookeeperCAP 理论

CAP 理论概述

著名的 CAP 理论告诉我们,一个分布式系统不可能同时满足以下三种

  • 一致性(C:Consistency
  • 可用性(A:Available
  • 分区容错性(P:Partition Tolerance

这三个基本需求,最多只能同时满足其中的两项,因为 P 是必须的,因此往往选择就在 CP或者 AP

一致性(C:Consistency

在分布式环境中,一致性是指数据在多个副本之间是否能够保持数据一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。例如一个将数据副本分布在不同分布式节点上的系统来说,如果对第一个节点的数据进行了更新操作并且更新成功后,其他节点上的数据也应该得到更新,并且所有用户都可以读取到其最新的值,那么这样的系统就被认为具有强一致性(或严格的一致性,最终一致性)

可用性(A:Available

可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。“有效的时间内”是指,对于用户的一个操作请求,系统必须能够在指定的时间(即响应时间)内返回对应的处理结果,如果超过了这个时间范围,那么系统就被认为是不可用的

分区容错性(P:Partition Tolerance

分区容错性约束了一个分布式系统需要具有如下特性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障

ZooKeeper 保证 CP

  • 不能保证每次服务请求的可用性。任何时刻对 ZooKeeper 的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性;但是它不能保证每次服务请求的可用性(注:也就是在极端环境下,ZooKeeper 可能会丢弃一些请求,消费者程序需要重新请求才能获得结果)。所以说,ZooKeeper不能保证服务可用性
  • 进行 leader 选举时集群都是不可用。在使用 ZooKeeper 获取服务列表时,当 leader 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举。问题在于,选举 leader 的时间太长(30 ~ 120s), 且选举期间整个 ZooKeeper 集群都是不可用的,这就导致在选举期间注册服务瘫痪,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。所以说,ZooKeeper 不能保证服务可用性

Eureka 保证 AP

Eureka 看明白了这一点,因此在设计时就优先保证可用性。Eureka 中各个节点都是平等的,几个节点挂掉不会影响正常节点的工作。剩余的节点依然可以提供注册和查询的服务。而 Eureka 的客户端在向某个 Eureka 注册时如果发现连接失败,则会自动切换至其他节点。只要有一台 Eureka 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)

除此之外,Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

  1. Eureka 不再从注册列表中移除因为长时间没有心跳而应该过期的服务
  2. Eureka 仍然能够接受新服务的注册和查询请求,但是不会同步到其他节点上(既保证当前节点依然可用)
  3. 当网络恢复正常时,当前实例新的注册信息会被同步到其他节点上 因此 Eureka 可以很好应对因网络故障导致部分节点失去联系的情况,而不会像 Zookeeper 那样使整个注册服务瘫痪

猜你喜欢

转载自blog.csdn.net/weixin_38192427/article/details/113839369