1.ZooKeeper概述
- Zookeeper是一个分布式协调服务的开源框架。主要用来解决分布式集群中应用系统的一致性问题。
- ZooKeeper本质上是一个小文件存储系统。提供基于类似于文件系统的目录树方式的数据存储,并且可以对树中的节点进行有效管理。从而用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。
2. ZooKeeper特性
- 全局数据一致:集群中每个服务器保存一份相同的数据副本,client无论连接到哪个服务器,展示的数据都是一致的,这是最重要的特征;
- 可靠性:如果消息被其中一台服务器接受,那么将被所有的服务器接受。
- 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
- 数据更新原子性:一次数据更新要么成功(半数以上节点成功),要么失败,不存在中间状态;
- 实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。
3. ZooKeeper集群角色

Leader:
Zookeeper集群工作的核心
事务请求(写操作)的唯一调度和处理者,保证集群事务处理的顺序性;
集群内部各个服务器的调度者。
对于create,setData,delete等有写操作的请求,则需要统一转发给leader处理,leader需要决定编号、执行操作,这个过程称为一个事务。
Follower:
处理客户端非事务(读操作)请求,转发事务请求给Leader;
参与集群Leader选举投票。
此外,针对访问量比较大的zookeeper集群,还可新增观察者角色。
Observer:
观察者角色,观察Zookeeper集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给Leader服务器进行处理。
不会参与任何形式的投票只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。
4.ZooKeeper集群搭建
- Zookeeper集群搭建指的是ZooKeeper分布式模式安装。通常由2n+1台server组成。这是因为为了保证Leader选举(基于Paxos算法的实现)能过得到多数的支持,所以ZooKeeper集群的数量一般为奇数。
- Zookeeper运行需要java环境,所以需要提前安装jdk。对于安装leader+follower模式的集群,大致过程如下:
- 配置主机名称到IP地址映射配置
- 修改ZooKeeper配置文件
- 远程复制分发安装文件
- 设置myid
- 启动ZooKeeper集群
#启动第一台节点的 zookeeper 服务 #在任意目录下执行 zkServer.sh start #单节点执行started #Using config: /export/server/zookeeper-3.4.6/bin/../conf/zoo.cfg #Error contacting service. It is probably not running. 需要分别启动三台节点。 原因是:要求zookeeper 集群的个数为 2*n + 1 ,否则不能够正常的执行,要保证zookeeper节点中最少有两台才能够启动起来。 问题 如果当前的leader 挂掉了 还会选择 leader吗? 答案:会的,还会使用zookeeper的旧机群选举策略,选择出leader。
- 详见安装文档zookeeper安装
5. Zookeeper数据模型

- 图中的每个节点称为一个Znode。 每个Znode由3部分组成:
- ZooKeeper的数据模型,在结构上和标准文件系统的非常相似,拥有一个层次的命名空间,都是采用树形层次结构,ZooKeeper树中的每个节点被称为—Znode。和文件系统的目录树一样,ZooKeeper树中的每个节点可以拥有子节点。但也有不同之处:
- Znode兼具文件和目录两种特点,既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分,并可以具有子Znode。用户对Znode具有增、删、改、查等操作(权限允许的情况下)。
- Znode具有原子性操作,读操作将获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。另外,每一个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作。
- Znode存储数据大小有限制,ZooKeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M,当时常规使用中应该远小于此值。
- Znode通过路径引用,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。字符串"/zookeeper"用以保存管理信息,比如关键配额信息。
① stat:此为状态信息, 描述该Znode的版本, 权限等信息
② data:与该Znode关联的数据
③ children:该Znode下的子节点
6. Zookeeper节点类型
- Znode有两种,分别为临时节点和永久节点。
- 节点的类型在创建时即被确定,并且不能改变。
- 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然可以也可以手动删除。临时节点不允许拥有子节点。
- 永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
- Znode还有一个序列化的特性,如果创建的时候指定的话,该Znode的名字后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一的,这样便会记录每个子节点创建的先后顺序。它的格式为“%10d”(10位数字,没有数值的数位用0补充,例如“0000000001”)。

这样便会存在四种类型的Znode节点,分别对应:
- PERSISTENT:永久节点
- EPHEMERAL:临时节点
- PERSISTENT_SEQUENTIAL:永久节点、序列化
- EPHEMERAL_SEQUENTIAL:临时节点、序列化
7. ZooKeeper的shell操作
zkCli.sh -server node1:2181,node2:2181,node3:2181
#创建节点的格式 create -s -e /spark node1:7077 #-s :序列化 #-e :是临时节点,如果客户端消失就没有了 #创建永久节点 create /hadoop node2:50070
#读取节点的内容 get /spark #获取当前的路径 ls /hadoop #获取当前路径的详细信息 ls2 /hadoop

# 格式 set path data set /spark host:node1,port:2181
# 删除节点 delete path # 如果 spark 下没有子目录就可以直接删除 delete /spark # 递归删除节点 rmr path 如果有子目录 rmr /spark
# 设置节点大小或者长度的限制 setquota -n 2 /spark setquota -b 10 /hadoop #如果当前显示的字节长度为 -1 ,代表当前的数据没有限制
delquota -n /spark
#查看当前限额
listquota /spark
history
# 指定监听目录 get /hadoop watch # 改变监听的目录 set /hadoop abcdefg # 拿到节点变化的通知 WatchedEvent state:SyncConnected type:NodeDataChanged path:/hadoop
-
客户端向服务器注册 watch
-
服务器事件如果发生了改变的wtch
-
事件封装
ZooKeeper使用WatchedEvent对象来封装服务端事件并传递。
WatchedEvent包含了每一个事件的三个基本属性:
通知状态(keeperState),事件类型(EventType)和节点路径(path)
event异步发送
watcher的通知事件从服务端发送到客户端是异步的。
先注册再触发
Zookeeper中的watch机制,必须客户端先去服务端注册监听,这样事件发送才会触发监听,通知给客户端。
<dependencies> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>com.google.collections</groupId> <artifactId>google-collections</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.25</version> </dependency> </dependencies> <build> <plugins> <!-- java编译插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build>
import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.CreateMode; import org.junit.Test; /** * Author itcast * Date 2020/8/16 16:50 * Desc 对zookeeper 进行 java api的 * 创建目录 * 修改数据 * 获取数据 * 删除数据 */ public class ZookeeperTest { @Test public void createPath() throws Exception { //1.java操作zookeeper客户端对象 String connectString = "node1:2181,node2:2181,node3:2181"; ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy); //2.启动客户端 client.start(); //3.创建目录操作 client.create().withMode(CreateMode.PERSISTENT).forPath("/helloworld", "helloworld".getBytes()); client.close(); } /** * 修改值 */ @Test public void setData() throws Exception { //1.java操作zookeeper客户端对象 String connectString = "node1:2181,node2:2181,node3:2181"; ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy); //2.启动客户端 client.start(); //3.修改值 client.setData().forPath("/helloworld","alibaba".getBytes()); client.close(); } /** * 获取节点的值 */ @Test public void getData() throws Exception { //1.java操作zookeeper客户端对象 String connectString = "node1:2181,node2:2181,node3:2181"; ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy); //2.启动客户端 client.start(); //3.修改值 byte[] bytes = client.getData().forPath("/helloworld"); System.out.println(new String(bytes,"utf-8")); client.close(); } /** * 删除指定节点的数据 */ @Test public void deletePath() throws Exception { //1.java操作zookeeper客户端对象 String connectString = "node1:2181,node2:2181,node3:2181"; ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy); //2.启动客户端 client.start(); //3.删除节点 client.delete().forPath("/helloworld"); client.close(); } }
-
-
服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
-
服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者 leader,服务器1,2成为小弟 follower。
-
服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟 follower。
-

-
-
-
服务器ID:就是我们配置的myid中的值,每个机器一个。
-
逻辑时钟:这个值从0开始递增,每次选举对应一个值。 如果在同一次选举中,这个值是一致的。
-
-
这样选举的标准就变成:
1、逻辑时钟小的选举结果被忽略,重新投票;
2、统一逻辑时钟后,数据id大的胜出;
3、数据id相同的情况下,服务器id大的胜出;

浙公网安备 33010602011771号