文章目录
一、什么是Zookeeper
1. Zookeeper的由来
摘自《从Paxos到Zookeeper 》第四章第一节的某段内容
Zookeeper最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的Pig项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家RaghuRamakrishnan开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而Zookeeper正好要用来进行分布式环境的协调一一于是,Zookeeper的名字也就由此诞生了。
2. Zookeeper概览
- 官方文档:ZooKeeper是一种集中式服务,用于维护配置信息,命名,提供分布式同步和提供组服务。所有这些类型的服务都以分布式应用程序的某种形式使用。每次实施它们都需要做很多工作来修复不可避免的错误和竞争条件。由于难以实现这些类型的服务,应用程序最初通常会吝啬它们,这使得它们在变化的情况下变得脆弱并且难以管理。即使正确完成,这些服务的不同实现也会在部署应用程序时导致管理复杂性。
- 个人理解:zookeeper=文件系统+监听通知机制。
- 文件系统
- 监听通知机制:客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。
- 最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心。
二、关于Zookeeper的一些重要概念
1. 重要概念总结
- ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)
- 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的
- ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持znode中存储的数据量较小的进一步原因)
- ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
- ZooKeeper有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上
- ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提交数据节点监听服务
2. 会话(Session)
Session
指的是 ZooKeeper 服务器与客户端会话- 在 ZooKeeper 中,一个客户端连接是指客户端和服务器之间的一个 TCP 长连接,通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的Watch事件通知(客户端启动的时候,首先会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。)
sessionTimeout
值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在超时时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。- 在为客户端创建会话之前,服务端首先会为每个客户端都分配一个sessionID。由于 sessionID 是 Zookeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一
3. Znode
在谈到分布式的时候,我们通常说的“节点"是指组成集群的每一台机器。然而,在Zookeeper中,“节点"分为两类,第一类同样是指构成集群的机器,我们称之为机器节点;第二类则是指数据模型中的数据单元,我们称之为数据节点一一ZNode
- Zookeeper将所有数据存储在内存中,数据模型是一棵树(Znode Tree),由斜杠(/)的进行分割的路径,就是一个Znode,例如/foo/path1。每个ZNode上都会保存自己的数据内容,同时还会保存一系列属性信息
- 在Zookeeper中,node可以分为持久节点和临时节点两类
- 持久节点:一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上
- 临时节点:生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。
- ZooKeeper还允许用户为每个节点添加一个特殊的属性:
SEQUENTIAL
.一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。
4. 版本
- Zookeeper 的每个 ZNode 上都会存储数据,对应于每个ZNode,Zookeeper 都会为其维护一个叫作
Stat
的数据结构 - Stat中记录了这个 ZNode 的三个数据版本,分别是
version
(当前ZNode的版本)、cversion
(当前ZNode子节点的版本)和cversion
(当前ZNode的ACL版本)
5. Watcher
Watcher
(事件监听器),是Zookeeper中的一个很重要的特性- Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性
6. ACL
- Zookeeper采用ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。Zookeeper 定义了如下5种权限。
CREATE
:创建子节点的权限READ
:获取节点数据和子节点列表的权限WRITE
:更新节点数据的权限DELETE
:删除子节点的权限ADMIN
:设置节点ACL的权限- 注意,
CREATE
和DELETE
这两种权限都是针对子节点的权限控制
- ACL 机制是以
scheme:id:permissions
来表示的,scheme
表示采用哪种机制,id
表示用户,permissions
表示相关权限。Zookeeper 总共提供了五中的scheme
world
(默认): 下面只有一个id
叫做anyone
,Zookeeper 中开放所有权限的结点就是world:anyone
auth
: 它不需要id
, 只要是通过认证的用户都有权限digest
: 它对应的id
为username:BASE64(SHA1(password))
,它需要先通过username:password
形式的认证ip
: 它对应的id
为客户机的 IP 地址,并且可以设置成一个 IP 段比如ip:192.168.1.0/16
super
: 在这种scheme
下,对应的id
拥有超级权限- 注意,ACL 没有继承机制,所有的 ZNode 创建后都需要重新设置 ACL 而无法继承上一级的
三、Zookeeper特点
- 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去
- 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用
- 单一系统映像 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的
- 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖
四、Zookeeper的设计目标
1. 简单的数据模型
ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相互协调,这与标准文件系统类似。 名称空间由 ZooKeeper 中的数据寄存器组成 - 称为znode,这些类似于文件和目录。 与为存储设计的典型文件系统不同,ZooKeeper数据保存在内存中,这意味着ZooKeeper可以实现高吞吐量和低延迟
2. 可构建集群
客户端在使用 ZooKeeper 时,需要知道集群机器列表,通过与集群中的某一台机器建立 TCP 连接来使用服务,客户端使用这个TCP链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上
上图中每一个Server代表一个安装Zookeeper服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 Zab 协议(Zookeeper Atomic Broadcast) 来保持数据的一致性
3. 顺序访问
对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。 这个编号也叫做时间戳——zxid(Zookeeper Transaction Id)
4. 高性能
ZooKeeper 是高性能的。 在 “读”多于“写” 的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
五、Zookeeper 集群角色介绍
- 最典型集群模式: Master/Slave 模式(主备模式)。在这种模式中,通常 Master服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务
- 在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三种角色
- ZooKeeper 集群中的所有机器通过一个 Leader 选举 过程来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
六、Zookeeper &ZAB 协议&Paxos算法
1. ZAB 协议&Paxos算法
Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在ZooKeeper的官方文档中也指出,ZAB协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为Zookeeper设计的崩溃可恢复的原子消息广播算法
2. ZAB 协议介绍
ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性
3. ZAB 协议两种基本的模式:崩溃恢复和消息广播
- 当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的Leader服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致
- 当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进人消息广播模式了。
- 当一台同样遵守ZAB协议的服务器启动后加人到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加人的服务器就会自觉地进人数据恢复模式:找到Leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去
- Leader服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议;而如果集群中的其他机器接收到客户端的事务请求,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器
- 图解 Paxos 一致性协议
- Zookeeper ZAB 协议分析
- Zookeeper 实现分布式锁
七、Zookeeper单机模式
1. 安装
- 配置JAVA环境,检验环境:
java -version
- 下载并解压zookeeper
# cd /usr/local
# wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.12.tar.gz
# tar -zxvf zookeeper-3.4.12.tar.gz
# cd zookeeper-3.4.12
- 重命名配置文件zoo_sample.cfg
# cp conf/zoo_sample.cfg conf/zoo.cfg
- 启动zookeeper
# bin/zkServer.sh start
- 检测是否成功启动,用zookeeper客户端连接下服务端
# bin/zkCli.sh
2. 使用客户端命令操作zookeeper
- 在客户端里输入
help
即可查看常用命令
- 使用
ls
命令来查看当前 ZooKeeper 中所包含的内容
ls /
- 创建一个新的 znode
create /zkPro myData
- 运行 get 命令来确认创建的 znode 是否包含我们所创建的字符串
get /zkPro
- 通过 set 命令来对 zk 所关联的字符串进行设置
set /zkPro MyData
- 删除创建的znode
delete /zkPro
3. 使用Java API操作zookeeper
- 需要添加以下依赖
- 在zookeeper里增加一个目录节点,并且把配置信息存储在里面
- 启动两个zookeeper客户端程序,代码如下所示
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
/**
* 分布式配置中心demo
* @author
*
*/
public class ZooKeeperProSync implements Watcher {
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
private static ZooKeeper zk = null;
private static Stat stat = new Stat();
public static void main(String[] args) throws Exception {
//zookeeper配置数据存放路径
String path = "/username";
//连接zookeeper并且注册一个默认的监听器
zk = new ZooKeeper("192.168.31.100:2181", 5000, //
new ZooKeeperProSync());
//等待zk连接成功的通知
connectedSemaphore.await();
//获取path目录节点的配置数据,并注册默认的监听器
System.out.println(new String(zk.getData(path, true, stat)));
Thread.sleep(Integer.MAX_VALUE);
}
public void process(WatchedEvent event) {
if (KeeperState.SyncConnected == event.getState()) { //zk连接成功通知事件
if (EventType.None == event.getType() && null == event.getPath()) {
connectedSemaphore.countDown();
} else if (event.getType() == EventType.NodeDataChanged) { //zk目录节点数据变化通知事件
try {
System.out.println("配置已修改,新值为:" + new String(zk.getData(event.getPath(), true, stat)));
} catch (Exception e) {
}
}
}
}
}
两个程序启动后都正确的读取到了zookeeper的/username目录节点下的数据qingfeng
- 我们在zookeeper里修改下目录节点/username下的数据
修改完成后,我们看见两个程序后台都及时收到了他们监听的目录节点数据变更后的值,如下所示
八、Zookeeper集群模式安装
本例搭建的是伪集群模式,即一台机器上启动三个zookeeper实例组成集群,真正的集群模式无非就是实例IP地址不同,搭建方法没有区别
- 前三步和单机模式一样
- 修改配置文件zoo-1.cfg,原配置文件里有的,修改成下面的值,没有的则加上
# vim conf/zoo-1.cfg
dataDir=/tmp/zookeeper-1
clientPort=2181
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
- 再从zoo-1.cfg复制两个配置文件zoo-2.cfg和zoo-3.cfg,只需修改dataDir和clientPort不同即可
# cp conf/zoo-1.cfg conf/zoo-2.cfg
# cp conf/zoo-1.cfg conf/zoo-3.cfg
# vim conf/zoo-2.cfg
dataDir=/tmp/zookeeper-2
clientPort=2182
# vim conf/zoo-2.cfg
dataDir=/tmp/zookeeper-3
clientPort=2183
- 标识Server ID,创建三个文件夹
/tmp/zookeeper-1
,/tmp/zookeeper-2
,/tmp/zookeeper-2
,在每个目录中创建文件myid
文件,写入当前实例的server id
,即1.2.3
# cd /tmp/zookeeper-1
# vim myid
1
# cd /tmp/zookeeper-2
# vim myid
2
# cd /tmp/zookeeper-3
# vim myid
3
- 启动三个zookeeper实例
# bin/zkServer.sh start conf/zoo-1.cfg
# bin/zkServer.sh start conf/zoo-2.cfg
# bin/zkServer.sh start conf/zoo-3.cfg
- 检测集群状态,也可以直接用命令
zkCli.sh -server IP:PORT
连接zookeeper服务端检测