前面的篇章中我们介绍了分布式系统中的CAP理论和BASE理论以及基于BASE理论的Raft算法,我们知道在分布式系统中最为复杂的就是解决分布式一致性的难题,在大数据生态中有一个工具,不仅自己实现了分布式的一致性服务并且还能为其他工具提供分布式一致性协调的服务,它就是Zookeeper,这个系列我们就深入学习一下zookeeper框架。

 

目录

简介

架构

数据模型

应用场景


简介

zookeeper是一个分布式的、为分布式应用提供协调服务的开源Apache项目,本质上它其实是个分布式文件系统,提供类似于文件系统的目录方式的数据存储,适合存放小文件。

Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。

zookeeper 替代品 zookeeper系列_hadoop

zookeeper有如下特点

  1. Zookeeper集群由一个领导者(Leader)和多个跟随者(Follower)组成。
  2. 集群中只要有半数以上节点存活,Zookeeper集群就能正常服务 。
  3. 全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的 。
  4. 有序性:更新请求顺序进行,来自同一个Client的更新请求按其发送顺序依次执行。
  5. 数据更新原子性,一次性数据更新要么成功,要么失败。
  6. 实时性,在一定时间范围内,Client能读到最新数据。

架构

Zookeeper是一个基于主从架构的高可用集群,主要包括Leader(领导者)、Learner(学习者)和Client(客户端)三大类角色,其中Learner又分为Follower(跟随者)和Observer(观察者)。

 

zookeeper 替代品 zookeeper系列_分布式_02

zk集群内的每个服务器承担下面三种角色中的一种:

Leader:一个zk集群同一时间只会有一个实际工作的Leader,它会发起并维护与各个Follower及Observer之间的心跳。所有的写操作必须要通过Leader完成再由Leader将写操作广播给其他服务器。

Follower:一个zk集群可能同时存在多个Follower,它会响应Leader的心跳。Follower可直接处理并返回客户端的读请求,同时会将写请求转发给Leader处理,并且负责在Leader处理写请求时对请求进行投票。

Observer:与Follower类似,但没有投票权,主要用于负载均衡,减轻Follower的负担。

角色

描述

Leader(领导者)

1.Leader是集群工作的核心,集群内部各个服务器的调度者;

2.Leader负责进行投票选举;

3.处理事务性写请求;

4.参与集群投票;

Follower(跟随者)

1.Follower用于接收客户端请求并向客户端返回结果;

2.处理客户端非事务读请求;

3. 转发事务请求给Leader;

4.参与集群投票;

Observer(观察者)

1.Observer用于接收客户端请求并向客户端返回结果;

2.处理客户端非事务读请求;

3. 转发事务请求给Leader;

4.不参与集群投票;

Client(客户端)

请求的发起方

数据模型

Zookeeper数据模型的结构与Unix文件系统很类似,拥有一个层次的命名空间,都是采用树形层次结构,zk数据模型中的每个节点被称为ZNode,和文件系统不一样的是,每个Znode同时具有文件夹和文件的属性。

zookeeper 替代品 zookeeper系列_分布式_03

Znode具有如下特点:

  • 兼具文件和目录两种特点:既像文件一样维护者数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径表示的一部分,并可与具有子Znode。在权限允许的情况下,用户可以对Znode进行增、删、改、查等操作。
  • 存储数据大小有限制:虽然Znode可以存储数据,但并不是被设计为常规的数据库或者大数据存储,相反的是,它常被用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为单位。zk的的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多为1M,常规使用中应该远小于此值。
  • 通过路径引用:如图Unix中的文件路径,路径必须是绝对路径,因此Znode必须由"/"字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在zk中,路径由unicode字符串组成,并且有一些限制,如字符串"/zookeeper"是默认保存管理信息。
  • 每个Znode由下面三部分组成
  • stat:状态信息,描述znode的版本和权限等信息。
  • data:与该znode关联的数据。
  • children:该znode下的子节点。

Znode节点类型

Znode节点有四种类型,且节点的类型在创建时就被确定了是无法变更的。

  • 临时节点(EPHEMERAL):该节点的生命周期依赖于创建它的会话,一旦会话结束临时节点就会被删除。临时节点不允许拥有子节点。
  • 永久节点(PERSISTENT):该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
  • 序列化临时节点(EPHEMERAL_SEQUENTIAL):具有临时节点的特性,且具有序列化特性。
  • 序列化永久节点(PERSISTENT_SEQUENTIAL):具有永久节点的特性,且具有序列化特性。

序列化节点: 该Znode的名字后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一的,这样便会记录每个子节点创建的先后顺序。它的格式为10位数字,没有数值的数位用0补充,如"zname0000000001"。

Znode节点属性

Znode的属性包括节点数据,状态,权限等信息:

属性

说明

cZxid

znode创建节点的事务ID,Zookeeper中每个变化都会产生一个全局唯一的zxid。通过它可确定更新操作的先后顺序

ctime

创建时间

mZxid

修改节点的事务ID。对于zk来说,每次的变化都会产生一个唯一的事务id,zxid(Zookeeper Transaction Id)。通过zxid,可以确定更新操作的先后顺序,例如zxid1小于zxid2,说明zxid操作先于zxid2发生,zxid对于整个zk都是唯一的。

mtime

最后修改时间

pZxid

子节点变更的事务ID,添加子节点或删除子节点就会影响子节点列表,但是修改子节点的数据内容则不影响该ID

cversion

子节点版本号,子节点每次修改时,版本号加1

dataversion

数据版本号,数据每次修改该版本号加1 (即使设置的是相同的数据),多个客户端对同一个znode进行更新操作时,因为数据版本号,才能保证更新操作的先后顺序性。例:客户端A正在对znode节点做更新操作,此时如果另一个客户端B同时更新了这个znode,则A的版本号已经过期,那么A调用setData不会成功。

aclversion

权限版本号,权限每次修改该版本号加1

dataLength

该节点的数据长度

numChildern

该节点拥有的子节点的数量

ephemeralOwner

如果该节点为临时节点,ephemeralOwner值表示与该节点绑定的session id,如果不是临时节点,则ephemeralOwner的值为0。

应用场景

Zookeeper提供的服务包括:数据发布订阅,统一命名服务,分布式协调通知,分布式锁,分布式队列等。

数据发布/订阅

发布者将数据发布到zk的znode上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。

发布/订阅一般有两种设计模式:推模式和拉模式。服务端主动将数据更新发送给所有订阅的客户端称为推模式;客户端主动请求获取最新数据称为拉模式。

zk采用了推拉相结合的模式,客户端向服务端注册自己需要关注的节点,一旦该节点数据发生变更,那么服务端就会向相应的客户端推送Watcher事件通知,客户端接收到此通知后,主动到服务端获取最新的数据。

统一命名服务

命名服务是分布式系统中较为常见的一类场景,在分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等,通过命名服务,客户端可以根据指定名字来获取资源的实体,在分布式环境中,上层应用仅仅需要一个全局唯一的名字。

zookeeper 替代品 zookeeper系列_zookeeper_04

Zookeeper可以实现一套分布式全局唯一ID的分配机制。通过调用Zookeeper创建节点的API接口就可以创建一个顺序节点,并且在API返回值中会返回这个节点的完整名字,利用此特性,可以生成全局ID,其步骤如下

  1. 客户端根据任务类型,在指定类型的任务下通过调用接口创建一个顺序节点,如"job-"。
  2. 创建完成后,会返回一个完整的节点名,如"job-00000001"。
  3. 客户端拼接type类型和返回值后,就可以作为全局唯一ID了,如"type2-job-00000001"。

zookeeper 替代品 zookeeper系列_原理_05

分布式协调/通知

Zookeeper中特有的Watcher注册异步通知机制,能够很好地实现分布式环境下不同的机器,甚至不同系统之间的协调与通知,从而实现对数据变更的实时处理。通常的做法是不同的客户端都对Zookeeper上的同一个数据节点进行Watcher注册,监听数据节点的变化(包括节点本身和子节点),若数据节点发生变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并作出相应处理。

在绝大多数分布式系统中,系统机器间的通信无外乎心跳检测、工作进度汇报和系统调度。

1. 心跳检测,不同机器间需要检测到彼此是否在正常运行,可以使用Zookeeper实现机器间的心跳检测,基于其临时节点特性(临时节点的生存周期是客户端会话,客户端若当即后,其临时节点自然不再存在),可以让不同机器都在Zookeeper的一个指定节点下创建临时子节点,不同的机器之间可以根据这个临时子节点来判断对应的客户端机器是否存活。通过Zookeeper可以大大减少系统耦合。

2. 工作进度汇报,通常任务被分发到不同机器后,需要实时地将自己的任务执行进度汇报给分发系统,可以在Zookeeper上选择一个节点,每个任务客户端都在这个节点下面创建临时子节点,这样不仅可以判断机器是否存活,同时各个机器可以将自己的任务执行进度写到该临时节点中去,以便中心系统能够实时获取任务的执行进度。

3. 系统调度,Zookeeper能够实现如下系统调度模式:分布式系统由控制台和一些客户端系统两部分构成,控制台的职责就是需要将一些指令信息发送给所有的客户端,以控制他们进行相应的业务逻辑,后台管理人员在控制台上做一些操作,实际上就是修改Zookeeper上某些节点的数据,Zookeeper可以把数据变更以时间通知的形式发送给订阅客户端。

zookeeper 替代品 zookeeper系列_原理_06

分布式锁

分布式锁用于控制分布式系统之间同步访问共享资源的一种方式,可以保证不同系统访问一个或一组资源时的一致性,主要分为排它锁和共享锁。

排它锁又称为写锁或独占锁,若事务T1对数据对象O1加上了排它锁,那么在整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作,直到T1释放了排它锁。

zookeeper 替代品 zookeeper系列_原理_07

1. 获取锁,在需要获取排它锁时,所有客户端通过调用接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。Zookeeper可以保证只有一个客户端能够创建成功,没有成功的客户端需要注册/exclusive_lock节点监听。

2. 释放锁,当获取锁的客户端宕机或者正常完成业务逻辑都会导致临时节点的删除,此时,所有在/exclusive_lock节点上注册监听的客户端都会收到通知,可以重新发起分布式锁获取。

共享锁又称为读锁,若事务T1对数据对象O1加上共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都被释放。在需要获取共享锁时,所有客户端都会到/shared_lock下面创建一个临时顺序节点。

zookeeper 替代品 zookeeper系列_zookeeper_08

分布式队列

有一些时候,多个团队需要共同完成一个任务,比如,A团队将Hadoop集群计算的结果交给B团队继续计算,B完成了自己任务再交给C团队继续做。这就有点像业务系统的工作流一样,一环一环地传下去.

分布式环境下,我们同样需要一个类似单进程队列的组件,用来实现跨进程、跨主机、跨网络的数据共享和数据传递,这就是分布式队列。