Zookeeper

TOC

[TOC]

分布式系统之间如何协作,信息同步和共享。

  • 通过网络进行信息共享
  • 通过共享存储

Zookeeper通过对节点的更新订阅,通知,达到信息共享。

Zookeeper的基本概念

开源的分布式协调服务。可以基于它实现数据订阅发布、负载均衡、命名服务、集群管理、分布式锁和分布式队列等功能。

基本概念

集群角色

Leader:选举产生,Leader为客户端提供读写服务

Follower:提供读服务

Observer:提供读服务,不参与Leader选举,不参与写操作过半写成功策略,可以在不影响写性能等情况下提升集群的性能

会话(session)

客户端会话,一个客户端连接是指客户端和服务端之间的一个TCP长连接

Zookeeper对外的服务端口默认为2181

客户端启动的时候,首先会与服务器建立一个TCP长连接,从第一次连接建立开始,客户

端的生命周期也开始了,通过这个连接,客户端能够心跳检测与服务端保持有效的会话,

也能够向Zookeeper服务器发送请求并接受响应,

同时还能通过该连接接受来自服务器的Watch事件通知。

数据节点(Znode)

节点的两种含义:机器节点,数据单元。

Zookeeper将所有数据存储在内存中,数据模型是一棵树,由斜杠(/)进行分割的路径,就是一个Znode,例如/app/path1。每个ZNode上都会保存自己的数据内容,同时还会保存一系列属性信息。

版本

对于每个ZNode,Zookeeper都会为期维护一个叫作Stat的数据结构,Stat记录了这个ZNode的三个版本,分别是

  • version(当前ZNode版本)
  • cversion(当前ZNode子节点的版本)
  • aversion(当前ZNode的ACL版本)

Watcher(事件监听器)

Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,Zookeeper服务端会将事件通知到感兴趣的客户端,该机制是Zookeeper实现分布式协调服务的重要特性。

ACL

Access Control Lists,权限控制,其定义了五种权限:

  • CREATE,创建自节点的权限
  • READ,获取节点数据和子节点列表的权限
  • WRITE,更新节点数据的权限
  • DELETE,删除子节点的权限
  • ADMIN,设置节点ACL的权限

其中需要注意的是,CREATE和DELETE这两种权限都是针对子节点的权限控制

Zookeeper环境等搭建

搭建方式

单机模式

  1. 下载
  2. 解压 tar -xzvf zookeeper-3.6.0.tar.gz
  3. 进入目录,创建data文件夹
cd zookeeper-3.6.0
mkdir data
  1. 修改配置文件

dataDir='你的zookeeper存放目录'/data

  1. 启动Zookeeper服务

进入bin目录,启动服务输入命令

./zkServer.sh start

  1. 关闭服务命令

./zkServer.sh stop

  1. 查看状态

./zkServer.sh status

伪集群模式

注意事项:

clientPort不同,

dataDir不同,

dataLogDir不同,

还要在dataDir所对应的目录中创建myid文件来指定对应Zookeeper服务器的实例

server.X这个数字就是对应,data/myid中的数字。在3个server的myid文件中分别写入了1,2,3那么每个server中的zoo.cfg都配了server.1 server.2 server.3就行了。因为在同一台机器上,后面连着的2个端口,3个server都不要一样,否则会端口冲突。

  1. 下载
  2. 解压,创建zkcluster目录

mkdir zkcluster

  1. 改变名称

mv zookeeper-3.6.0 zookeeper01

  1. 复制并改名
cp -r zookeeper01/ zookeeper02
cp -r zookeeper01/ zookeeper03
  1. 分别在三个zookeeper根目录下创建data和logs目录
mkdir data
cd data
mkdir logs
  1. 修改配置文件名称
cd conf
mv zoo.sample.cfg zoo.cfg
  1. 配置每一个ZK的dataDir(zoo.cfg)clientPort分别为2181,2182,2183
clientPort=2181
dataDir=/zkcluster/zookeeper01/data
dataLogDir=/zkcluster/zookeeper01/data/logs
clientPort=2182
dataDir=/zkcluster/zookeeper02/data
dataLogDir=/zkcluster/zookeeper02/data/logs
clientPort=2183
dataDir=/zkcluster/zookeeper03/data
dataLogDir=/zkcluster/zookeeper03/data/logs
  1. 配置集群

在每个zookeeper的data目录下创建一个myid文件,内容分别是1,2,3。这个文件就是记录每个服务器的ID。

touch myid

在每个zookeeper的zoo.cfg文件中配置客户端访问端口和集群服务器IP列表

server.1=192.168.101.25:2181:3881
server.2=192.168.101.25:2182:3882
server.3=192.168.101.25:2183:3882
#server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口
  1. 启动集群
image-20200428073640324

Zookeeper的基本使用

Zookeeper系统模型

Zookeeper数据模型ZNode

image-20200428075537809

ZNode的类型

  • 持久性节点(Persistent)
  • 临时性节点(Ephemeral)
  • 顺序性节点(Sequential)

在开发中,创建节点的时候通过组合可以生成以下四种节点类型:持久节点、持久顺序节点、临时节点、临时顺序节点。

持久节点:节点被创建后,会一直存在服务器,直到服务器操作主动删除

持久顺序节点:持久性和上面一样,顺序特性,就是节点在创建的时候,在节点名后面添加一个数字后缀,来表示其顺序。

临时节点:会被自动清理的节点,客户端会话结束,节点就会被删除。临时节点不能创建子节点。

临时顺序节点:有顺序的临时节点

事务ID

狭义上的事务是指数据库事务,一般包含了一系列对数据库有序的读写,这些数据库事务具有ACID特性,即原子性、一致性、隔离性、持久性。

在Zookeeper中,事务是指能够改变Zookeeper服务器状态的操作,也称为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新等操作。对于每一个事务请求,Zookeeper都会为其分配一个全局唯一的事务ID,用ZXID来表示,通常是一个64位的数字。每一个ZXID对应一次更新操作,从这些ZXID中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺心。

ZNode的状态信息

image-20200428201329190

整个ZNode节点内容包括两部分:节点数据内容和节点状态信息。图中quota是数据内容,其他的属于状态信息。这些信息的含义是:

cZxid就是create zxid,表示节点被创建时的事务id。

crime就是create time,表示节点创建时间。

mzxid就是modified zxid,表示节点最后一次被修改时的事务id。

mtime就是modified time,表示节点最后一次被修改的事件。

pzxid表示该节点的子节点列表最后一次被修改时的事务ID。只有子节点列表变更才会更新pZxid,子节点内容变更不会更新。

cversion,表示子节点的版本号。

dataVersion表示内容版本号

aclVersion标识acl版本

ephemeralOwner表示创建该临时节点时的会话sessionID,如果持久性节点那么值为0.

dataLength表示数据长度

numChildren表示直系子节点数。

Watcher--数据变更通知

ACL--保障数据安全

Zookeeper命令行操作

创建节点

读取节点

更新节点

删除节点

Zookeeper的api使用

建立会话

创建节点

获取节点数据

修改节点数据

删除节点

Zookeeper开源客户端

ZKClient

Curator

作业题:

image-20200504121253816

Zookeeper应用场景

数据发布/订阅

可以实现配置信息的集中式管理和数据的动态更新。

发布订阅系统一般有两种设计模式,分别是推模式和拉模式。

Zookeeper采用推拉结合的方式:

客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据。

使用Zookeeper来进行配置集中管理,一般流程:

应用在启动的时候主动到Zookeeper服务端上进行一次配置信息的获取,同时,在指定节点上注册一个Watcher监听,这样一来,一旦配置信息变更,服务端会实时通知所有订阅的客户端,从而达到实时获取最新配置信息的目的。

机器列表信息、运行时的开关配置、数据库配置信息等可以在这个场景实现。

命名服务

什么是命名服务,客户端根据指定名字来获取资源的实体、服务地址和提供者的信息。

命名服务一要名字有意义,二要全局唯一。

为什么不用uuid

  1. 长度过长
  2. 含义不明

如何使用Zookeeper实现一套分布式全局唯一ID的分配机制。

使用到的Zookeeper特性是:顺序节点,每一个数据节点都能够维护一份子节点的顺序序列,当客户端对其创建一个顺序子节点的时候,Zookeeper会自动以后缀的形式在其子节点上添加一个序号。

image-20200430150912398

全局唯一ID生成的Zookeeper节点示意图。

对于一个任务列表的主键,使用Zookeeper生成唯一ID的基本步骤:

  1. 所有客户端都会根据自己的任务类型,在指定类型的任务下面通过调用create接口创建一个顺序节点,例如创建"job-"节点
  2. 节点创建完毕后,create接口会返回一个完整的节点名,例如"job-00000000003"
  3. 客户端拿到这个返回值后,拼接上type类型,例如"type2-job-00000000003",这个就可以作为一个全局唯一的ID了。

集群管理

包括集群监控和集群控制。

集群监控:侧重对集群运行时状态的收集

集群管理:对集群进行操作与控制

Agent集群管理

  • 大规模升级困难
  • 统一的Agent无法满足多样的需求
  • 对机器的物理状态监控可以满足,但无法满足业务状态的监控。

分布式消息中间件中,监控到每个消费者对消息的消费状态;分布式任务调度系统中,需要对每个机器上任务的执行情况进行监控

  • 编程语言的多样性

Zookeeper的两大特性

  1. 客户端如果对Zookeeper的数据节点注册Watcher监听,那么当该数据节点的内容或其子节点列表发生变更时,Zookeeper服务器就会向客户端发送变更通知。
  2. 对在Zookeeper上创建的临时节点,一旦客户端与服务器之间的会话失效,那么临时节点也会被自动删除。

利用其两大特性,可以实现集群机器的存活监控,若监控系统在/clusterServers节点上注册一个Watcher监听,一旦进行动态添加机器的操作,就会在/clusterServers节点下创建一个临时节点:/clusterServers/[HostName],这样,监控系统就能够实时监测机器的变动情况。

分布式日志收集系统

做什么的:收集分布在不同机器上的系统日志。

日志源机器(需要收集日志的机器)分为多个组别,每个组别对应一个收集器。

问题:

  • 变化的日志源机器

  • 变化的收集器机器

如何快速、合理、动态地为每个收集器分配对应的日志源机器?

使用Zookeeper:

  1. 注册收集器机器

在Zookeeper上创建一个节点作为收集器的根节点,例如/logs/collector,每个收集器机器在启动的时候,都会在收集器节点下创建自己的节点,例如/logs/collector/[HostName]

image-20200501201448118
  1. 任务分发

所有收集器机器都创建好对应都节点后,系统根据收集器节点下子节点的个数,将所有日志源机器分成对应的若干组,然后将分组后的(日志源)机器列表分别写到这些收集器机器创建的子节点(/logs/collector/host1)上去。这样一来,每个收集器机器都能够从自己对应的收集器节点上获取日志源机器列表,进而开始进行日志收集工作。

图中有3台收集器机器,假如日志源机器有9台,分组后,每个收集器机器需要写入三台日志源机器。

  1. 状态汇报

目的:考虑机器挂掉的可能

收集器的状态汇报机制:每个收集器机器在创建完自己的专属节点后,还需要在对应的子节点上创建一个状态子节点,收集器机器定期写入状态信息,日志系统查询状态子节点的最后更新时间来判断对应的收集器机器是否存活。

  1. 动态分配

日志系统关注/logs/collector节点下的变更。一旦有变更,机器假如或者停止,就会重新分配。

  • 全局动态分配

出现收集器机器挂掉或者新机器加入的时候,日志系统需要根据新的收集器机器列表,立即对所有的日志源机器重新进行一次分组,然后将其分配给剩下的收集器机器。

  • 局部动态分配

why:全局动态分配影响面比较大,风险比较大

what:小范围内进行任务的动态分配

how:每个收集器机器在汇报自己日志收集状态的同时,也会把自己的负载汇报上去。这里的负载不仅仅是CPU Load,也是对当前收集器任务执行的综合评估。

如果一个收集器机器挂了,那么日志系统就会把之前分配给这个机器的任务重新分配到那些负载较低的机器上去。同样,如果有新的收集器机器加入,会从那些负载高的机器上转移部分任务给这个新加入的机器。

两点注意事项:

  1. 节点类型的选择

由于收集器节点记录了日志源机器列表,所以不能直接使用临时节点表示每个收集器节点。需要用持久节点。在收集器节点下创建状态节点,用来记录收集器的状态

  1. 日志系统节点监听

若采用Watcher机制,那么通知的消息量的网络开销非常大,需要采用日志系统主动轮询收集器节点的策略,这样可以节省网络流量,但是存在一定的延时。

Master选举

利用Zookeeper的强一致性,能够很好保证在分布式高并发情况下,节点的创建一定能够保证全局唯一性,即Zookeeper将会保证客户端无法重复创建一个已经存在的节点。

image-20200504100633330

客户端集群每天定时往Zookeeper上创建一个临时节点,例如/master_election/2020-11-11/binding。在这个过程中,只有一个客户端能够成功创建这个节点,那么这个客户端所在机器就成了Master。

其他没有创建成功的客户端,会在节点/master_election/2020-11-11上注册一个子节点变更的Watcher,用于监控当前Master机器是否存活,一旦发现Master挂了,那么其余客户端将会重新进行Master选举。

  • 保证数据唯一性
  • 保证Master故障后能够通知其他节点可以重新选举

分布式锁

排他锁

又称写锁或独占锁。如果当前事务获取了排他锁,只有当前事务可以对共享资源读取和更新,其他事务只能等待当前事务释放锁才能操作。

  1. 定义锁

在/exclusive-lock节点下创建临时子节点/exclusive-lock/lock。

  1. 获取锁

创建临时子节点/exclusive-lock/lock的过程就是获取锁的过程,Zookeeper可以保证多个客户端只有一个可以创建同一个节点成功。其他没有获取到锁的客户端需要到/exclusive-lock节点上注册一个子节点变更的Watcher监听,以便实时监听lock节点的变更情况

  1. 释放锁

/exclusive-lock/lock是一个临时节点,两种情况会释放锁

  • 获取锁的客户端宕机,临时节点会自动移除
  • 获取锁的客户端正常执行完业务,客户端主动删除临时节点。

释放锁后,其他没有获取锁的客户端会收到通知,再次发起分布式锁的获取。

image-20200504120734250

共享锁

如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁——直到该数据对象上的所有共享锁都被释放。

共享锁和排他锁最根本的区别在于,加上排他锁后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务都可见

下面看看如何借助Zookeeper实现共享锁。

  1. 定义锁

通过Zookeeper上的数据节点表示锁,通过/shared_lock/[Hostname]-请求类型-序号的临时顺序节点,例如/shared_lock/host1-R-0000001,这个节点就代表一个共享锁。

image-20200504155508006
  1. 获取锁

获取锁的时候,所有客户端都会到shared_lock节点下创建临时顺序节点,如果当前是读请求,就创建如/shared_lock/host1-R-0000001节点;如果是写请求,就创建例如/shared_lock/host1-W-0000002的节点。

判断读写顺序

通过Zookeeper来确定分布式读写顺序,大致分为四步

1. 创建完节点后,获取/shared_lock节点下所有子节点,并对该节点变更注册监听
2. 确定自己的节点序号在所有子节点中的顺序
3. 对于读请求:若没有比自己序号小的子节点或所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到共享锁,同时开始执行读取逻辑,若有写请求,则需要等待。对于写请求:若自己不是序号最小的子节点,那么需要等待。
4. 接收到Watcher通知后,重复步骤1.
  1. 释放锁,与与独占锁一致

羊群效应

当锁下面的竞争机器数超过10,很大时,由于是在/shared_lock上注册的监听,如果同一时间有多个节点对应的客户端完成事务或者事务中断引起节点消失,Zookeeper服务器就会在短时间内向其余客户端发送大量的事件通知。这就是羊群效应。

没有找准客户端真正的关注点。判断自己是否是所有子节点中序号最小的。每个节点对应的客户端只需要关注比自己序号小的那个相关节点的变更情况就可以了,而不是关注全局的子列表变更情况。

  1. 客户端调用create接口创建类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点

  2. 客户端调用getChildren接口获取所有已经创建的子节点列表(不注册任何Watcher)

  3. 如果无法获取共享锁,就调用exsit接口来对比自己小的节点注册Watcher。对于读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。对于写请求:向比自己序号小的最后一个节点注册Watcher监听。

  4. 等待Watcher通知,继续进入步骤2

image-20200504172259727

如何选择:根据项目的实际情况选择两种方式的一种。

分布式队列

  • FIFO先进先出队列模型

  • 等待队列元素聚集后统一安排处理执行的Barrier模型

FIFO队列类似于一个全写的共享锁模型,思路:所有客户端都会到/queue_fifo这个节点下创建一个临时顺序节点,例如/queue_fifo/host1-0000001

image-20200504172927445

创建完节点后,根据如下4个步骤来确定执行顺序

  1. 通过调用getChildren接口来获取/queue_fifo节点的所有子节点,即获取队列中所有的元素
  2. 确定自己的节点序号在所有子节点中的顺序
  3. 如果自己的序号不是最小,那么需要等待,同时向比自己序号小的最后一个节点注册Watcher监听
  4. 接收到Watcher通知后,重复步骤1.
image-20200504173154258

Barrier:分布式屏障

场景:大规模并行计算的应用,最终的合并计算需要基于很多并行计算的子结果来进行。

思路:开始时,/queue_fifo节点是一个已经存在的默认节点,并且将其节点的数据内容赋值一个数字n来代表Barrier值,例如n=10表示只有当/queue_fifo节点下的子节点个数达到10后,才会打开Barrier。所有的客户端都会到/queue_fifo节点下创建一个临时节点,例如/queue_fifo/host1,如图所示

image-20200505091725135

创建完节点后,按照如下步骤执行

  1. 通过调用getData接口获取/queue_barrier节点的数据内容:10
  2. 通过调用getChildren接口获取/queue_barrier节点下的所有子节点,同时注册对子节点变更的Watcher监听
  3. 统计子节点的个数
  4. 如果子节点个数还不足10个,那么需要等待
  5. 接收到Watcher通知后,重复步骤2
image-20200505100106500

Zookeeper深入进阶

ZAB协议

Zookeeper原子广播协议

一种支持崩溃恢复到原子广播协议

主备模式的系统架构,保证集群中各副本之间的数据一致性。使用一个单一的主进程来接收并处理客户端的所有事务请求,并采用ZAB原子广播协议,将服务器数据的状态变更以事务Proposal的形式广播到所有的副本进程中。

基于ZAB协议如何处理请求

请求由Leader处理,Leader服务器负责将一个客户端事务转化成一个事务Proposal,并将Proposal分发给集群中所有的Follower服务器,之后需要等待所有的Follower服务器反馈,只要收到超过半数的正确反馈后,Leader就会再次向所有的Follower分发commit消息,要求其将前一个Proposal提交。

image-20200505124828302

ZAB协议介绍

两个基本的模式:崩溃恢复和消息广播

  1. 进入崩溃恢复模式

什么时候进入?

启动过程,网络中断、崩溃推出或重启

进入崩溃恢复模式会进行Leader选举

什么时候退出崩溃恢复模式

集群中有半数以上机器与Leader同步完状态后,就退出崩溃恢复模式

状态同步:就是数据同步

  1. 进入消息广播模式

当集群中已经有过半数Follower服务器完成了与Leader服务器的状态同步,就进入消息广播模式

当新加入一台同样遵守ZAB协议的服务器到集群中,且集群中已经有一个Leader在负责消息广播,那么加入到服务器就自觉进入数据恢复模式:找到Leader所在的服务器,并于其进行数据同步,然后一起参与到消息广播流程中去。

只有Leader服务器可以进行事务请求的处理,如果其他机器收到客户端请求后,非Leader服务器会首先将这个事务请求转发到Leader服务器。

消息广播过程和崩溃恢复过程

  1. 消息广播

Leader服务器会为每一个Follower服务器都各自分配一个单独的队列,然后将需要广播的事务Proposal依次放入这些队列中去,并且根据FIFO策略进行消息发送。

每一个Follower服务器在接收到这个事务Proposal之后,都会首先将其以事务日志的形式写入到本地磁盘中,并且在写入成功后反馈给Leader服务器一个Ack响应。

当Leader服务器接收到过半数Follower的Ack响应后,就会广播一个Commit消息给所有Follower服务器以通知其进行事务提交,同时Leader自身也会完成对事务的提交。

而每一个Follower服务器在接收到Commit消息后,也会完成事务的提交。

  1. 崩溃恢复

崩溃恢复需要考虑什么?

恢复过程会选出一个新的Leader,需要一个高效可靠的选举算法,不仅仅Leader自身知道已经被选举为Leader,还需要集群中其他机器也能够快速的感知到选举产生出来的新Leader机器。

  • 需要确保那些已经在Leader服务器上提交的事务最终被所有服务器都提交
  • 需要确保丢弃那些只在Leader服务器上被提出的事务

小结:Leader选举算法需要确保提交已经被Leader提交的事务,同时丢弃已经被跳过的事务。

让具有最高事务编号的Proposal的机器成为Leader(行文到这里,依然不是很清楚怎么做到的崩溃恢复,只知道一个结论)

数据同步过程

Leader确认事务日志中的所有Proposal是否都已经被集群中过半数的机器提交了,即是否完成数据同步。

具体的过程:Leader服务器为每一个Follower服务器准备一个队列,并将那些没有被各Follower服务器同步的事务以Propoal的形式逐个发送给Follower服务器,并在每一个Proposal消息后面,紧接着发送一个Commit消息,以表示该事务已经被提交。

等到Follower服务器将所有尚未同步的事务都从Leader服务器同步过来,并成功应用到本地数据库后,Leader服务器就会将该Follower服务器加入到真正可用的Follower列表中,并开始之后的其他流程。

运行状态分析

在ZAB协议的设计中,每个进程都有可能处于如下三种状态之一

  • LOOKING:进程初始状态是LOOKING,Leader选举阶段其余Follower进程也是LOOKING状态
  • FOLLOWING:Follower服务器和Leader服务器保持同步状态
  • LEADING:Leader服务器作为主进程领导状态

Leader能够在超时时间内正常收到过半心跳检测,或者TCP连接断开,那么Leader会放弃当前周期的领导,并转换为LOOKING状态,其他Follower会选择放弃这个Leader,同时转换到LOOING状态,之后会进行新一轮的Leader选举。

ZAB与Paxos的联系和区别

联系

  • 都有Leader和Follower的角色

  • Leader进程都会等待过半数Follower作出正确的反馈后,才会将一个提议进行提交

  • 都有一个表示当前Leader周期的值,ZAB叫epoch,Paxos叫Ballot

区别

Paxos中,新选举产生的Leader会进行两阶段的工作,读阶段,新的主进程和其他进程通信来收集主进程提出的提议,并将他们提交,第二阶段写,当前主进程开始提出自己的提议。

ZAB协议在Paxos基础上添加了同步阶段,此时,新的Leader会确保存在过半数的Follower已经提交了之前的Leader周期中的所有事务Proposal。

主要区别是因为他们的设计目标不同,ZAB主要用于构建一个高可用的分布式数据主备系统,Paxos用于构建一个分布式的一致性状态机系统

服务器角色

Leader主要工作

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

Leader请求处理链

image-20200505171038581

Follower主要工作

  • 处理客户端非事务请求(读取请求),转发事务请求给leader服务器
  • 参与事务请求Proposal的投票
  • 参与Leader选举的投票

Follower请求的处理链

image-20200505172303940

Observer

观察Zookeeper集群的最新状态变化,并将这些状态变更同步过来。

对于非事务请求(读取),可以处理,对于事务请求,转发给Leader处理。

不参与任何形式的投票,包括事务请求的Proposal投票和Leader选举投票。

通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。

Observer处理请求链

image-20200505173753110

服务器启动

服务器整体架构

image-20200505173831506

Zookeeper服务器启动步骤

  1. 配置文件解析
  2. 初始化数据管理器
  3. 初始化网络IO管理器
  4. 数据恢复
  5. 对外服务

单机版服务器启动

image-20200505174058529

集群服务器启动

image-20200505174126533

Leader选举

当Zookeeper集群中的一台服务器出现以下两种情况之一时,需要进入Leader选举:

  1. 服务器初始化启动
  2. 服务器运行期间无法和Leader保持连接

启动时的选举

  1. 每个server发出一个投票,投票用(myid,ZXID)表示
  2. 接受来自各个服务器的投票
  3. 处理投票:先比较ZXID,大的作为Leader,否则再比较myid。
  4. 统计投票
  5. 改变服务器状态

运行时的选举

  1. 变更状态
  2. 每个server会发出一个投票
  3. 接收来自各个服务器的投票,与启动时过程相同
  4. 处理投票
  5. 统计投票
  6. 改变服务器状态

Zookeeper源码分析

源码环境搭建

将准备好的zookeeper-release-3.5.4导入idea中

启动服务器端

运行主类org.apache.zookeeper.server.ZookeeperServerMain,将zoo.cfg的完整路径配置在Program arguments。

image-20200505193904354

在VM options配置,即指定到conf目录下的log4j.properties:

-Dlog4j.configuration=file:/path-to-zk/conf/log4j.properties

运行输出日志如下

image-20200505194110174

可以得知单机版启动成功,单机版服务端地址为127.0.0.1:2182

运行客户端

image-20200505194321204

客户端启动类为org.apache.zookeeper.ZooKeeperMain,进行如下配置:

即客户端连接127.0.0.1:2182,获取节点/lg的信息。

单机模式服务端启动

Leader选举

Election接口

FastLeaderElection

默认选举策略

重要的内部类:

Notification

ToSend

Messenger

Messenger的内部类:

WorkerReceiver:选票接收器,不断地从QuorumCnxManager获取其他服务器发来的选举消息,并将其转成一个选票,然后保存到recvqueue中,如果接收过程中,发现该外部选票轮次小于当前服务器的,忽略该外部投票,同时立即发送自己的内部投票。将QuorumCnxManager的Message转化为FastLeaderElection的Notification。

WorkerSender:选票发送器,不断的从sendQueue中获取待发送的选票,并将其传递到底层的QuorumCnxManager中,过程是将FastLeaderElection的ToSend转化为QuorumCnxManager的Message。

类的属性

image-20200505201224910

类的构造函数

image-20200505201254631

会启动WorkerSender和WorkerReceiver,并设置为守护线程。

集群模式服务端

Time waits for no one.