ZooKeeper原理


1、简介

Zookeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储,但是 Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。

简单的说,zookeeper=文件系统+通知机制。


2、特性

最终一致性

保证各个节点服务器数据能够最终达成一致,zk的招牌功能

顺序性

从同一客户端发起的事务请求,都会最终被严格的按照其发送顺序被应用到zk中,这也是zk选举leader的依据之一

可靠性

凡是服务器成功的使用一个事务,并完成了客户端的响应,那么这个事务所引起的服务端状态变更会被一直保留下去

实时性

zk不能保证多个客户端能同时得到刚更新的数据,所以如果要最新数据,需要在读数据之前强制调用sync接口来保证数据的实时性

原子性

数据更新要么成功要么失败

单一视图

无论客户端连的是哪个节点,看到的数据模型对外一致


3、原理

3.1、角色介绍

  • 领导者(leader):负责进行投票的发起和决议,更新系统状态。
  • 学习者(learner):包括跟随者(follower)和观察者(observer),follower用于接受客户端请求并想客户端返回结果,在选主过程中参与投票。
  • Observer:可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度。
  • 客户端(client):请求发起方。

 

3.2、数据模型

Zookeeper 会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统,每个节点都叫数据节点(znode),节点上可以存储数据。

数据结构的特点:

  • 每个子目录项如 NameService 都被称作为 znode,这个 znode 是被它所在的路径唯一标识,如 Server1 这个 znode 的标识为 /NameService/Server1
  • znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL 类型的目录节点不能有子节点目录
  • znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据
  • znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,Zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了
  • znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2
  • znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基于这个特性实现的。

节点的类型:

  • PERSISTENT-持久化目录节点
  • 客户端与zookeeper断开连接后,该节点依旧存在
  • PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
  • 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  • EPHEMERAL-临时目录节点
  • 客户端与zookeeper断开连接后,该节点被删除
  • EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
  • 客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

3.3、读写流程

 

只有leader才能真正处理事务请求,在整个集群中,当follower和observer收到来自客户端的事务请求时,会转发给leader。当leader处理事务请求的时候,他会向整个集群广播一个提议,也就是我们常说的zab,这个提议的意思就是告诉follower你们要创建,修改,删除某znode,然后follower接受leader的提议之后呢,就会做出相应的操作,并告诉leader操作已经完成。

当leader接受到集群里大多数follower的成功操作的回复之后,(这里大多数指的是集群里机器数的一半,这也是zk为啥要使用奇数台机器凑成集群的一个依据之一),leader认为这次事务被成功处理了,接着再向所有的follower进行通知,告知其进行事务提交,最后会返回客户端一个事务被成功处理的状态。此时如果有落后的follower也就是还没来得及进行事务同步的那些,也会从leader去同步状态,来保证与leader的一致状态。

数据写入最终一致性zab算法。leader负责处理写事务请求,follower负责向leader转发写请求,响应leader发出的提议。

 

3.4、zk 选举

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

Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。

为了保证事务的顺序一致性,zookeeper采用了递增的 事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。

每个Server在工作过程中有三种状态:

  • LOOKING:当前Server不知道leader是谁,正在搜寻
  • LEADING:当前Server即为选举出来的leader
  • FOLLOWING:leader已经选举出来,当前Server与之同步

observer:观察者状态

zk选主流程

首先每个节点发出一个投票,内容是(myid,zxid),接受来自各个节点的投票,接着进行投票的处理和统计,最后改变服务器的状态


4、应用

4.1、配置管理

这个好理解,分布式系统都有好多机器,比如我在搭建hadoop的HDFS的时候,需要在一个主机器上(Master节点)配置好HDFS需要的各种配置文件,然后通过scp命令把这些配置文件拷贝到其他节点上,这样各个机器拿到的配置信息是一致的,才能成功运行起来HDFS服务。

Zookeeper提供了这样的一种服务:一种集中管理配置的方法,我们在这个集中的地方修改了配置,所有对这个配置感兴趣的都可以获得变更。这样就省去手动拷贝配置了,还保证了可靠和一致性。

 

4.2、 名字服务

这个可以简单理解为一个电话薄,电话号码不好记,但是人名好记,要打谁的电话,直接查人名就好了。

分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务;

  • 类似于域名与ip之间对应关系,域名容易记住;
  • 通过名称来获取资源或服务的地址,提供者等信息

4.3、分布式锁

碰到分布二字貌似就难理解了,其实很简单。单机程序的各个进程需要对互斥资源进行访问时需要加锁,那分布式程序分布在各个主机上的进程对互斥资源进行访问时也需要加锁。很多分布式系统有多个可服务的窗口,但是在某个时刻只让一个服务去干活,当这台服务出问题的时候锁释放,立即fail over到另外的服务。这在很多分布式系统中都是这么做,这种设计有一个更好听的名字叫Leader Election(leader选举)。举个通俗点的例子,比如银行取钱,有多个窗口,但是呢对你来说,只能有一个窗口对你服务,如果正在对你服务的窗口的柜员突然有急事走了,那咋办?找大堂经理(zookeeper)!大堂经理指定另外的一个窗口继续为你服务!

4.4、集群管理

在分布式的集群中,经常会由于各种原因,比如硬件故障,软件故障,网络问题,有些节点会进进出出。有新的节点加入进来,也有老的节点退出集群。这个时候,集群中有些机器(比如Master节点)需要感知到这种变化,然后根据这种变化做出对应的决策。我已经知道HDFS中namenode是通过datanode的心跳机制来实现上述感知的,那么我们可以先假设Zookeeper其实也是实现了类似心跳机制的功能吧!

4.5 队列管理

Zookeeper 可以处理两种类型的队列:

当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。同步队列用 Zookeeper 实现的实现思路如下: 创建一个父目录 /synchronizing,每个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列, 加入队列的方式就是创建 /synchronizing/member_i 的临时目录节点,然后每个成员获取 / synchronizing 目录的所有目录节点, 也就是 member_i。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start。

FIFO 队列用 Zookeeper 实现思路如下: 实现的思路也非常简单,就是在特定的目录下创建 SEQUENTIAL 类型的子目录 /queue_i,这样就能保证所有成员加入队列时都是有编号的, 出队列时通过 getChildren( ) 方法可以返回当前所有的队列中的元素,然后消费其中最小的一个,这样就能保证 FIFO。

4.6、master选举

 

 

master选举应用图

 

有个容易理解的方案,依靠关系型数据库主键的特性,集群的机器同时往一张表里插入数据,数据库会自动进行主键冲突检查,可以选择插入成功的客户端作为master

这种方式存在一个问题就是,master机器挂了,没有人通知

zk实现可以方便做到这一点:zk的创建节点api接口,具有强一致性,能够保证客户端并发创建的时候,最终一定只有一个客户端创建成功。

  • 客户端集群每天定时往/master_election/(当天日期)2017-03-24/binding创 建临时节点,只有一个成功,为master
  • 创建失败的在/master_election/(当天日期)2017-03-24 注册监听事件,当master挂了,其余机器收到通知,重新进行选举