Zookeeper系列一:基础概念
对于每个接触过大数据开发的同学而言,Zookeeper一定是不陌生的。它是一个开源的分布式服务框架,主要的用处就是为其他分布式框架的稳定运行提供服务。它有很多应用场景,比如分布式配置管理、分布式锁等。
笔者将从架构设计、数据模型、选举机制、读写数据流程、Watch机制五个方面展开。
架构设计
通常来讲,Zookeeper中主要有三个角色:
- 领导者(Leader)
- 管理协调整个集群的核心角色
- 写操作的唯一处理者,以便保证来自相同客户端的请求保持发送顺序执行
- 跟随者(Follower)
- 可处理读操作请求,如果遇到写操作请求,则需转发给Leader处理
- 参与集群中的选举投票
- 观察者(Observer)
- 为了满足集群访问量过大的需求而设立,不参与选举投票
- 可处理读操作请求,如果遇到写操作请求,则需转发给Leader处理
数据模型
Zookeeper中的数据模型很简单,和标准文件系统类似,采用树形结构,每个节点称之为Znode,根节点为/。
Znode可以存储数据,至多可以存储1M数据,也可以创建子节点。需要注意的是当通过路径引用节点时,必须采用绝对路径。
在Zookeeper中,存在四种类型的Znode节点:
- 永久节点:创建后一直存在,除非人为删除
- 临时节点:只存在于连接会话期间,会话结束就会被删除,临时节点不能有子节点
- 永久节点、序列化:在永久节点的基础上,追加递增序号
- 临时节点、序列化:在临时节点的基础上,追加递增序号
每个Znode主要由三部分组成: - stat:状态信息,描述Znode的版本、权限等信息
- data:存储的数据
- children:子节点
# 查看所有命令选项
help
# 查看根路径下所有节点
ls /
# 创建普通节点
create /app1 "helloworld"
# 创建带序号的永久节点
create -s /app2 "app2"
# 创建临时节点
create -e /app3 "app3"
# 创建带序号的临时节点
create -e -s /app4 "app4"
# 查看节点数据
get /app1
# 修改节点数据
set /app1 "app1"
# 删除节点
delete /app1
# 递归删除节点
rmr /app1
# 查看节点状态
stat /app3
选举机制
前面提过Zookeeper中是有Leader、Follower、Observer三种角色的,可是我们在配置文件中并没有指定这些角色,那么它们是怎样产生的呢?这就不得不提Zookeeper中的选举机制了。
假设有三台服务器组成的Zookeeper集群,它们的id分别是从1-3,那么当它们第一次启动后,谁才会成为Leader呢?我们简单地分析一下这个流程:
- 服务器1启动,发起一次选举。服务器1投自己一票,此时得票数为一票,不够半数以上,选举无法完成,服务器1状态保持为LOOKING;
- 服务器2启动,发起一次选举。服务器1和2分别投自己一票并交换选票信息。此时服务器1发现服务器2的id比自己目前投票选举的id(服务器1)要大,更改选票为服务器2。此时服务器1票数为0,服务器2票数为2,达到半数以上,服务器2当选为Leader。服务器1更改状态为FOLLOWING,服务器2更改状态为LEADING;
- 服务器3启动,发起一次选举。此时服务器1、2已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器2为2票,服务器3为1票。此时服务器3服从多数,更改选票信息为服务器2,并更改状态为FOLLOWING;
- 至此选举过程结束。
以上只是确定了Leader和Follower两类角色,那么Observer该如何确定呢?
其实Observer是通过配置文件实现的: - 在所有将会配置为Observer的节点配置文件zoo.cfg中添加如下语句:
peerType=observer
- 其次,在所有服务器节点的zoo.cfg配置文件中,observer对应的 server定义处末尾添加:observer:
server.1=bigdata01:2888:3888:observer
读写数据流程
写数据流程
我们知道znode节点可以存储数据,但是在一个分布式系统中,如何才能保证数据的一致性呢?
- 客户端向Zookeeper集群node1发送一个写数据请求;
- 如果node1不是Leader,则会将写请求转发给Leader。Leader会将这个写请求广播给各个节点,各个节点会将写请求加入待写队列,并向Leader发送成功信息;
- 当Leader受到半数以上节点成功信息后,说明该写操作可以执行。Leader回向各个节点发送提交信息,各个节点收到信息后会落实队列中的写请求,此时写操作成功;
- node1节点会通知客户端写操作成功,此时可以认为整个写操作成功。
读数据流程
相比写数据流程,读数据流程就简单得多;因为每台server中数据一致性都一样,所以随便访问哪台server读数据就行;没有写数据流程中请求转发、数据同步、成功通知这些步骤。
Watch机制
概念
一个典型的发布/订阅模型系统定义了一种一对多的订阅关系,能让多个订阅者同时监听某一个主题对象。当这个主题对象自身状态变化时,会通知所有订阅者,使它们都做出相应的处理。
而在Zookeeper中,就是引入了Watch机制来实现这种分布式的通知功能。
触发通知事件的种类很多,包括节点创建、节点删除、节点改变、子节点改变等。
总体来说,Watch可以分为以下三个过程:
- 客户端向服务端注册Watch监听事件
- 服务端事件发生触发Watch机制
- 客户端回调Watch得到触发事件情况
# 创建普通节点
create /app1 "ap1"
# 监听节点值变化
get /app1 watch
# 监听子节点变化
ls /app1 watch
特点
总体而言,Zookeeper中的Watch机制有以下特点:
- 一次性触发
- 事件发生触发监听后,一个watch事件就会被发送到设置监听的客户端
- 这是一次性的,后续再次发生同样的事件,不会再次触发
- 事件封装
- Zookeeper中使用WatchedEvent对象来封装服务端事件并传递
- WatchedEvent包含了每一个事件的三个基本属性:通知状态、事件类型和节点路径
- event异步发送
- watcher的通知事件从服务端发送到客户端是异步的
- 先注册再触发
- Zookeeper中的watch机制,必须客户端先去服务端注册监听,这样事件发送才会触发监听,通知给客户端
实现原理
Zookeeper中的Watch机制实现原理如下:
- 主进程main进程
- 在主进程中创建Zookeeper客户端同时会启动两个线程,一个是负责网络通信,一个负责监听
- 通过连接通信线程把注册的监听器事件发送给Zookeeper
- 在Zookeeper的监听器列表中把注册的监听器加入
- Zookeeper监听到节点数据变化或者路径变化,就会把此消息发送给监听线程
- 监听线程在内部会调用process方法,执行相关操作