ZooKeeper原理及使用

1 简介

ZooKeeper最主要的使用场景是作为分布式系统的分布式协同服务,保证分布式系统信息的一致性。分布式应用程序可以基于它实现诸如数据订阅/发布、负载均衡、命名服务、集群管理、分布式锁和分布式队列等功能。

下面介绍一些基本概念。

①集群角色:  Zookeeper中的所有机器通过Leader选举来选定一台被称为Leader的机器, Leader服务器为客户端提供读和写服务,除Leader外,其它机器包括Follower和Observer, Follower和Observer都能提供读服务,唯一的区别在于Oberserver不参与Leader选举过程,不参与写操作的过半写成功策略,因此Observer可以在不影响写性能的情况下提升集群的性能。

②会话(session): Session指客户端会话,一个客户端连接是指客户端和服务端之间的一个TCP长连接,通过这个连接,客户端能够心跳检测与服务器保持有效的会话,也能够向ZooKeeper服务器发送请求并接受响应,同时还能够通过该连接接受来自服务器的Watch事件通知。

③数据节点: 分为俩类。第一类是指构成集群的机器,称之为机器节点。第二类是指数据模型中的数据单元,称之为数据节点-ZNode。ZooKeeper将所有的数据存储在内存中,数据模型是一颗树,采用文件系统的层级树结构进行管理,由/进行分割的路径就是一个ZNode。每个ZNode上都保存自己的数据内容,还会保存一系列属性信息。

版本: 每个ZNode, ZooKeeper都会维护一个叫Stat的数据结构,Stat记录了这个ZNode的三个数据版本,分别是version(当前ZNode的版本),cversion(当前ZNode子节点的版本),aversion(当前ZNode的ACL版本)。

④Watcher(事件监听器): ZooKeeper允许用户在指定节点上注册一些Watcher, 特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端。

⑤ACL: ZooKeeper采用ACL(Access Control Lists)策略进行权限控制,有五种权限。CREATE:创建子节点。READ:获取节点数据和子节点列表。WRITE:更新节点。DELETE:删除子节点。ADMIN:设置节点ACL权限。注意:CREATE和DELETE都是对子节点的权限控制。

2 环境搭建

 2.1 单机模式搭建

下载稳定版本的zookeeper http://zookeeper.apache.org/releases.html。上传到linux系统。解压缩压缩包

tar -zxvf zookeeper-3.4.14.tar.gz

进入ZooKeeper-3.4.14目录,创建data文件夹

cd zookeeper-3.4.14
mkdir data

修改配置文件名称

cd conf
mv zoo_sample.cfg zoo.cfg

修改zoo.cfg中的data属性

dataDir=/root/zookeeper-3.4.14/data

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

./zkServer.sh start

关闭服务命令

./zkServer.sh stop

查看状态

./zkServer.sh status

 2.2 伪集群模式

在单机上模拟集群模式ZooKeeper的运行,用端口进行区分。测试的时候,可以使用少量数据在伪集群模式下进行测试。测试成功后,再进行真实的数据试验。

注意事项:必须保证端口号不能冲突,dataDir也不同,还要在dataDir所对应的目录中创建myid文件来指定对应的ZooKeeper服务器实例。

复制并改名

cp -r zookeeper01/ zookeeper02
cp -r zookeeper01/ zookeeper03

分别在zookeeper01, zookeeper02, zookeeper03目录下创建data及logs目录

mkdir data
cd data
mkdir logs

修改配置文件名称

cd conf
mv zoo_sample.cfg zoo.cfg

配置每一个ZooKeeper的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

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

touch myid

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

server.1=10.211.55.4:2881:3881
server.2=10.211.55.4:2882:3882
server.3=10.211.55.4:2883:3883
#server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口

 

3 ZooKeeper的基本使用

 3.1 ZooKeeper数据模型ZNode

Znode的类型有以下四种。

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

持久顺序节点: 有顺序的持久节点。创建节点的时候,会在节点名后面加上一个数字后缀,来表示其顺序。

临时节点:就是会被自动清理掉的节点, 它的声明周期和客户端会话绑在一起,客户端会话结束,节点会被删除掉。与持久性节点不同的是,临时节点不能创建子节点。

临时顺序节点:就是有顺序的临时节点。在其创建的时候会在名字后面加上数字后缀。

事务ID: 数据节点创建与删除,数据节点内容更新都是事务。每一个事务请求,ZooKeeper会为其分配一个全局唯一的事务ID,用ZXID表示,一个64位的数字每一个ZXID对应一次更新操作。

ACL保障数据安全。权限模式用来确定权限验证过程中使用的校验策略,有如下四种模式:

1. IP  通过IP地址进行权限控制,如“ip:192.168.0.110”表示权限控制针对该IP地址,按网段方式进行配置, 如“ip:192.168.0.1/24”表示针对192.168.0.*这个网段进行权限控制。

2.Digest  使用“username:password”的形式配置权限,ZooKeeper会先后对其进行SHA-1加密和BASE64编码。

3.World 对所有用户开放 "world:anyone"

4.Super 超级用户,可以对任意ZooKeeper上的数据节点进行任何操作。

 3.2 ZooKeeper命令行操作

进入ZooKeeper的bin目录之后,通过zkClient进入zookeeper客户端命令行

./zkcli.sh 连接本地zookeeper服务器
./zkCli.sh -server ip:port 连接指定的服务器

创建节点

create [-s][-e] path data acl
其中,-s或-e分别指定节点特性,顺序或临时节点,不指定创建持久节点; acl用来进行权限控制

创建顺序节点

create -s /zk-test 123

创建临时节点

create -e /zk-temp 123

退出客户端

quit

获取节点下面的所有子节点,使用ls /

ls /

获取节点属性 3.6及以上版本ls -s /zk-permanent

ls -s /zk-permanent

获取节点数据内容

get /zk-permanent

更新节点 set path data [version]

set /zk-permanent 456

删除节点 delete path [version]

delete /zk-permanent

删除有子节点的节点

deleteall /zk-permanent

3.3 ZooKeeper开源客户端

ZkClient, 在pom文件中添加以下内容。

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.2</version>
</dependency>

相关方法调用

//建立连接
ZkClient zkClient = new ZkClient("127.0.0.1:2181");
//创建节点 zkClient.createPersistent(
"/lg-zkClient/lg-c1",true);
//删除节点会删除子节点 zkClient.deleteRecursive(path);
//获取自己节点 List
<String> children = zkClient.getChildren("/lg-zkClient");
//注册Watcher监听 zkClient.subscribeChildChanges(path,
new IZkChildListener() { public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {     System.out.println(parentPath + " 's child changed, currentChilds:" + currentChilds);   } }); //判断节点是否存在 boolean exists = zkClient.exists(path);
//获取节点 Object o
= zkClient.readData(path);
//更新节点 zkClient.writeData(path,
"4567");
//删除节点 zkClient.delete(path);

Curator客户端,最流行的ZooKeeper客户端之一。从编码风格上来讲,它提供了基于Fluent的编程风格支持。pom文件添加如下内容。

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>

相关API使用

//建立连接
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework Client = CuratorFrameworkFactory.builder()   .connectString("server1:2181,server2:2181,server3:2181")   .sessionTimeoutMs(50000)   .connectionTimeoutMs(30000)   .retryPolicy(retryPolicy)   .namespace("base")   .build(); client.start();
//检查节点是否存在
client.checkExists().forPath("path");
//创建节点
client.create().forPath(path);

//创建包含内容的节点
client.create().forPath(path,"123".getBytes());

//递归创建节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPa
th(path);

//添加节点
String path = "/lg-curator/c1";
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path,"init".getBytes());

//删除节点
client.delete().forPath(path);

//删除节点并递归删除子节点
client.delete().deletingChildrenIfNeeded().forPath(path);

//指定版本删除
client.delete().withVersion(1).forPath(path);

//强制保证删除一个节点
client.delete().guaranteed().forPath(path);

//获取节点
client.getData().forPath(path);

//包含状态查询
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath(path);

//更新节点
client.setData().forPath(path,"123".getBytes());

//指定版本更新
client.setData().withVersion(1).forPath(path);
//对指定的路径节点的一级子目录进行监听,不对该节点的操作进行监听,对其子目录的节点进行增、删、改的操作监听 
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, path, false); PathChildrenCacheListener pathChildrenCacheListener = new PathChildrenCacheListener() { @Override public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception { System.out.println("节点变更了" + pathChildrenCacheEvent.getType()); PathChildrenCacheEvent.Type type = pathChildrenCacheEvent.getType(); if (type == PathChildrenCacheEvent.Type.CHILD_ADDED || type == PathChildrenCacheEvent.Type.CHILD_REMOVED) { } } }; pathChildrenCache.getListenable().addListener(pathChildrenCacheListener); pathChildrenCache.start();

//
TreeCache 可以将指定的路径节点作为根节点(祖先节点),对其所有的子节点操作进行监听,呈现树形目录的监听,可以设置监听深度,最大监听深度为2147483647
//NodeCache 对一个节点进行监听,监听事件包括指定的路径节点的增、删、改的操作

3.4 ZooKeeper的应用场景

数据订阅/发布: 举个例子多台机器获取数据库配置信息 1.需要管理的配置信息写入到某个节点中 2.每台机器启动阶段从ZooKeeper节点上读取数据库信息并在该节点上注册一个数据变更的Watcher监听,一旦发生节点数据变更,所有订阅的客户端都能获取到数据变更。3.如果想要更改配置信息只需改ZooKeeper节点信息,就会通知客户端。

命名服务: 在分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等-这些我们统称他们为名字(Name), 常见的就是一些分布式服务框架(如PRC、PRI)中的服务地址列表,通过使用命名服务,客户端应用能够根据指定名字获取资源的实体、服务地址和提供者的信息。命名需要生成一个唯一ID。

ZooKeeper生成唯一节点功能过程。

 

1. 所有客户都会根据自己的任务类型,在指定类型的任务下面通过调用create()接口来创建一个顺序节点,例如创建"job-"节点。

2.节点创建完毕后,create()接口会返回一个完整的节点名,例如"job-0000000003"。

3.客户端拿到这个返回值后,拼接上type类型,例如"type2-job-000000003",这就可以作为一个全局唯一的ID了。

在ZooKeeper中,每一个数据节点都能够维护一份子节点的顺序,当客户端对其创建一个顺序子节点的时候ZooKeeper会自动以后缀的形式在其子节点上添加一个序号,在这个场景中就是利用了ZooKeeper的这个特性。

集群管理:所谓集群管理包括集群监控与集群控制俩大块,前者侧重对集群运行时状态的收集,后者则是对集群进行操作与控制。ZooKeeper有以下俩大特性。1.客户端如果对ZooKeeper的数据节点注册Watcher监听,那么当该数据节点的内容或是子节点列表发生变更时,ZooKeeper服务器就会向订阅的客户端发送变更通知。2.对在Zookeeper上创建的临时节点,一旦客户端与服务器之间的会话失效,那么临时节点也会被自动删除。

利用其俩大特性,可以实现集群机器存活监控系统,在节点上添加监听,添加机器,机器宕机都会发送通知。这样就可以实时监测机器的变动情况。

分布式日志收集系统: 在一个典型的日志系统的架构设计中,日志系统会把所有需要收集的日志机器(日志源机器)分为多个组别,每个组别对应一个收集器,这个收集器就是一个后台机器(收集器机器)用于收集日志。日志源机器或是收集器机器变更(硬件损坏,断网等)后,需要快速合理动态地为每个收集器分配对应的日志源机器。使用ZooKeeper来解决步骤如下

①注册收集器机器: 在ZooKeeper上创建一个节点作为收集器根节点,例如/logs/collector, 收集器启动时,都会在收集器节点下创建自己的节点,例如/logs/collector/hostname。

②任务分发: 所有收集器创建好节点后,系统根据收集器节点下子节点个数,将所有日志源机器分成对应的若干组,然后将分组后的机器列表分别写到这些收集器机器创建的子节点上去。这样每个收集器机器能够从自己对应的节点上获取日志源机器列表,进行日志收集工作。

③状态汇报:考虑到机器随时有挂掉的可能。我们需要一个状态汇报机制。每个收集器机器在创建完自己的专属节点后,还需要在对应的子节点上创建一个状态子节点。例如/logs/collector/host1/status,每个收集器机器需要定期向该节点写入自己的状态信息。可以认为是心跳检测机制,通常收集器机器都会在这个节点写入日志收集进度信息。日志系统根据该状态子节点的最后更新时间来判断对应的收集器是否存活。

④动态分配: 如果收集器机器挂掉或扩容,需要进行任务动态分配。日志系统始终关注着/logs/collector这个节点下所有子节点的变更,一旦检测到收集器机器停止汇报或是有新的收集器加入,就开始任务的重新分配。分配方式俩种

全局动态分配:对所有的日志源机器重新进行一次分组,然后将其分配给剩下的收集器机器。

局部动态分配:小范围内进行任务分配,每个收集器机器在汇报自己日志手机状态的同时,也会把自己的负载汇报上去。如果这个收集器挂了,日志系统就会把这个机器的任务重新分配到负载较低的机器上去。有新机器加入,会从那些负载高的机器上转移部分任务给这个新加入的机器。

注意事项: 1. 在/logs/collector节点下创建临时节点很好判断机器是否存活。但是机器挂了,节点会被删除。需要持久节点来标识每一台机器,同时在节点下分别创建/logs/collector/[Hostname]/status节点来表征每一个收集器的状态。2.采用Watch机制,通知的消息量的网络开销非常大,需要日志系统主动轮询收集器节点的策略,节省网络流量,但是存在一定的延时。

Master选举: Master往往是用来协调集群中其它系统单元,对分布式系统状态变更具有决定权。写请求及复杂的逻辑一般都是Master处理。我们可以利用ZooKeeper的强一致性,ZooKeeper会保证客户端无法创建一个已经存在的数据节点。利用这个特性,可以进行Master选举。客户端集群每天都会定时往ZooKeeper上创建临时节点,节点路径相同。只有一个客户端能够成功创建,它会成为Master。其它没有成功创建节点的客户端会在这点节点上注册一个节点变更的Watcher,如果Master挂了,其它客户端会重新进行Master选举。

分布式锁: 分布式锁是控制分布式系统之间同步访问共享资源的一种方式。排他锁又称写锁或独占锁。如果事务T1对数据对象O1加上排他锁,那么在释放锁之前,只允许事务T1对O1进行读取和更新操作。Java开发编程中通常用synchronized机制和JDK5提供的ReentrantLock。ZooKeeper中没有类似于这样的API可以直接使用,而是通过ZooKeeper上的数据节点来表示一个锁。获取排他锁时,所有客户端都会试图创建一个临时节点,只有一个客户能创建成功,他就获取了锁。没获取到锁的会在子节点上注册一个Watcher监听,以便实时监听到lock节点的变更情况。客户端宕机或完成逻辑后都会删除临时节点,即释放锁。注册了子节点变更的客户端就可以执行重复"获取锁"的操作。共享锁又称为读锁,如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其它事务也只能对这个数据对象加共享锁--直到数据对象上的所有共享锁都被释放。ZooKeeper在获取共享锁时,所有客户端都会在指定节点下创建一个临时顺序节点,如果是读请求就创建R-00001节点,如果是写请求就创建W-00002节点。通过ZooKeeper确定分布式读写顺序分为4步。1.创建完节点后,对父节点变更注册监听。2.确定自己的节点序号在所有节点中的顺序。3.对于读请求:若没有比自己序号小的子节点或所有比自己小的节点都是读请求,那么表示自己已经成功获取到共享锁,开始执行读取逻辑,若有写请求,则需要等待。对于写请求,若自己不是序号最小的子节点,那么需要等待。4.接收到Watcher通知后,重复步骤1。 释放锁与独占锁一致。共享锁和排他锁的区别,加上排他锁后,数据对象只对一个事务可见,加上共享锁后,数据对所有事务都可见。羊群效应:执行完任何一个读或写操作后会删除节点并发通知,大量的Watcher通知和子节点列表获取俩个操作会重复运行,会造成性能影响和网络开销。如果同一时间多个节点完成事务或事务中断,ZooKeeper服务器就会在短时间内发送大量的事件通知,这就是羊群效应。解决办法:对于读请求,向比自己序号小的最后一个写请求节点注册Watcher监听。对于写请求:向比自己序号小的最后一个节点注册 Watcher监听。

分布式队列:分布式队列可以分为俩大类,一是常规的FIFO先入先出队列模型,还有一种是等待队列元素聚集后统一安排处理执行的Barrier模型。

①FIFO先入先出。使用ZooKeeper实现FIFO队列:客户端都会在某个节点下创建一个临时顺序子节点。通过4个步骤来确定执行顺序。1.获取该节点的所有子节点。2.确定自己的节点序号在所有子节点中的顺序。3.如果自己的序号不是最小,需要等待,同时向比自己序号小的最后一个节点注册Watcher监听。4.接收到Watcher通知后,重复步骤1。

②Barrier  开始时,/queue_barrier是一个默认存在的节点,将其数据内容赋值为一个数字n来代表Barrier值,例如n=10表示只有当节点下的子节点个数达到10后,才会打开Barrier。主要是以下4个步骤:1.通过调用getData接口获取/queue_barrier节点的数据内容:10.  2. 获取节点下所有子节点,同时注册对子节点变更的Watcher监听。 3. 统计子节点的个数,如果不足10个继续等待。4. 接收到Watcher通知后重复步骤2。

4  ZAB协议

概念:ZooKeeper没有采用paxos算法。而是使用了一种称为ZooKeeper Atomic Broadcast(ZAB, ZooKeeper原子消息广播协议)的协议作为其数据一致性的核心算法。

核心:定义了那些会改变ZooKeeper服务器数据状态的事务请求的处理方式。所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为Leader服务器。余下的服务器为Follower服务器,Leader服务器负责将一个客户端事务请求转化为一个事务Proposal(提议), 并将该Proposal分发给集群中所有的Follower服务器,之后Leader服务器需要等待所有Follower服务器的反馈,一旦超过半数的Follower服务器进行了正确的反馈后,那么Leader就会再次向所有的Follower服务器分发Commit消息,要求其将前一个Proposal进行提交。

ZAB协议俩种模式:崩溃恢复和消息广播

崩溃恢复模式:当Leader服务器宕机后,ZAB协议就会进入崩溃恢复模式,同时选举新的Leader服务器。选举产生新的Leader服务器,同时集群中已经有过半的机器与该Leader完成状态同步之后,ZAB协议就会退出恢复模式。

消息广播模式:当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,就进入消息广播模式。当一台新的服务器加入到集群中,如果此时集群已经有Leader服务器在负责进行消息广播,那么加入的服务器就自觉地进入数据恢复模式。找到Leader,同步数据。然后一起参与到消息广播流程中去。ZooKeeper只允许唯一的一个Leader服务器进行事务请求的处理,Leader服务器在接收到客户端的事务请求后,会生成对应的事务提议并发起一轮广播协议,其他服务器收到客户端的事务请求后,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。

ZAB与Paxos的联系和区别

联系:①都存在一个类似Leader进程的角色,负责协调多个Follower进行的运行。②Leader进程都会等待超过半数的Follower做出正确的反馈后,才会将一个提议进行提交。③在ZAB协议中,每个Proposal中都包含了一个epoch值,用来代表当前的Leader周期,在Paxos算法中,同样存在这样的标识,名字为Ballot。

区别:Paxos算法中,新选举产生的主进程会进行俩个阶段的工作,第一阶段成为读阶段,新的主进程和其它进程通信来收集主进程提出的提议,并将它们提交。第二阶段成为写阶段,当前主进程开始提出自己的提议。ZAB协议在Paxos基础上添加了同步阶段,此时,新的Leader会确保存在过半的Follower已经提交了之前的Leader周期中的所有事务Proposal。这一同步阶段的引入,能够有效地保证Leader在新的周期中提出事务Proposal之前,所有的进程都已经完成了对之前所有事务Proposal的提交。总的来说,ZAB协议和Paxos算法的本质区别在于,俩者的设计目标不太一样,ZAB协议主要用于构建一个高可用的分布式数据主备系统,而Paxos算法则用于构建一个分布式的一致性状态机系统。

 服务器角色

Leader: ZooKeeper集群工作的核心,1)事务请求的唯一调度和处理者,保证集群事务处理的顺序性。2)集群内部各服务器的调度者。

Follower: ZooKeeper集群状态中的跟随者,1)处理客户端非事务性请求(读取数据), 转发事务请求给Leader服务器。 2)参与事务请求Proposal的投票。 3)参与Leader选举投票。

Observer: ZooKeeper进群状态中的观察者。工作原理和Follower基本是一致的,对于非事务请求都可以进行独立的处理。对于事务请求会交给Leader服务器。和Follower唯一的区别在于,Observe不参与任何形式的投票,包括事务请求Proposal的投票和Leader选举投票。简单地讲,Observer服务器只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。

5  Leader选举

Leader选举是zookeeper最重要的技术之一,也是保证分布式一致性的关键所在。当ZooKeeper集群中的一台服务器出现以下俩种情况之一时,进入Leader选举。

1.服务器初始化启动

服务器初始化启动后,都会将自己作为Leader服务器来进行投票,投票包含所推举的服务器的myid和ZXID。然后将这个投票发给集群中其他机器。其他机器投票规则:优先检查ZXID, ZXID比较大的服务器优先作为Leader。如果ZXID相同, 那么就比较myid。myid较大的服务器作为Leader服务器。投票完成后,统计投票,选票数过半则成为Leader服务器。确定Leader后,Leader服务器更新自己的状态为LEADING,Follower服务器更新为FOLLOWING。

2.服务器运行时期的Leader选举

Leader挂后,余下的非Observer服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。余下过程与初始化启动时相同,先投给自己,再让其他机器投票,产生Leader后更新状态。

posted on 2020-10-30 17:57  lvguoliang(学无止境)  阅读(273)  评论(0编辑  收藏  举报