手写RPC框架-zookeeper搭建和集群
一、概述
- 概念:解释ZooKeeper是一个开源的分布式协调服务组件,用于构建可靠的分布式系统。它提供了一个高性能的、有序的、可靠的分布式数据存储和协调服务,简单的理解为一个内存数据库,特殊的数据结构和一些特性,他可以实现一些特殊的功能。
- 应用场景:在分布式系统中我们经常使用ZooKeeper实现服务注册发现、分布式锁、配置管理、命名服务和分布式协调等功能。
- 数据模型:ZooKeeper的数据模型是一个类似于文件系统的层次结构。每个节点称为ZNode,它可以存储数据和子节点。zookeeper中的ZNode可以分为持久节点和临时节点。
- Watcher机制:ZooKeeper允许开发人员在节点上设置监视点,以便在节点发生更改时接收通知。
二、数据模型
ZooKeeper的数据模型是一个类似于文件系统的层次结构,*被组织成一个树形结构,每个节点称为ZNode。ZNode是ZooKeeper中的基本数据单元,它可以存储*数据和子节点。
以下是ZooKeeper数据模型的详细说明:
-
树形结构:
ZooKeeper的数据模型是一个树形结构,类似于文件系统的目录结构。整个树由根节点(称为"/")开始,每个节点可以有多个子节点。
每个节点都有一个路径来唯一标识它,路径是由斜杠(/)分隔的一系列名称组成。
-
ZNode:
ZNode是ZooKeeper的基本数据单元,类似于文件系统中的文件或目录。
每个ZNode可以存储一个字节数组作为其数据,可以是任意类型的数据,例如配置信息、状态信息等。
每个ZNode还可以有多个子节点,形成层次结构。
-
持久节点(Persistent Node):
持久节点在创建后一直存在,直到主动删除。
持久节点的数据和子节点都是持久的,即它们在节点创建后仍然存在,直到被显式删除。
-
临时节点(Ephemeral Node):
临时节点的生命周期与客户端会话绑定,当会话结束(例如客户端关闭或与ZooKeeper集合的连接断开)时,临时节点将自动被删除。
临时节点的数据和子节点也将随之被删除。
-
顺序节点(Sequential Node):
顺序节点在创建时会自动分配一个全局唯一且递增的编号。
顺序节点的编号由ZooKeeper集合维护,可以用于实现有序性或生成全局唯一的标识符。
三、watcher机制
Watcher机制是ZooKeeper中非常重要的概念,它允许客户端在ZooKeeper上设置监视点,以便在节点发生变化时接收通知。Watcher机制使得开发人员可以及时获取关于数据变化的通知,以便采取相应的操作。
以下是Watcher机制的详细说明:
-
注册Watcher:
客户端可以通过在对节点进行操作时注册Watcher来设置监视点。例如,在创建、更新或删除节点时都可以注册Watcher。
客户端在注册Watcher时需要指定监视的路径和Watcher对象。当指定路径的节点发生变化时,ZooKeeper会将通知发送给客户端。
-
Watcher通知:
当一个节点发生变化时,ZooKeeper会将通知发送给注册了Watcher的客户端。
Watcher通知是一次性的,也就是说,当客户端接收到Watcher通知后,该Watcher将被移除,需要客户端重新注册Watcher才能再次接收通知。
-
Watcher的类型:
数据变更触发的Watcher(Data Watcher):当节点的数据发生变化时触发,例如节点的值被修改。
子节点变更触发的Watcher(Child Watcher):当节点的子节点发生变化时触发,例如新增或删除子节点。
连接状态变更触发的Watcher(Existence Watcher):当客户端与ZooKeeper集合的连接状态发生变化时触发,例如连接断开或重新连接。
-
Watcher的触发流程:
当一个节点发生变化时,ZooKeeper首先会将通知发送给与该节点相关的Watcher。
客户端接收到Watcher通知后,需要处理通知并根据需要采取相应的操作,例如重新读取数据或重新注册Watcher。
-
Watcher的注意事项:
Watcher通知是异步的,客户端需要保证处理Watcher通知的代码是线程安全的。
Watcher通知是最终一致性的,即ZooKeeper不能保证Watcher通知的实时性,只能保证最终一致性。
Watcher通知是有序的,当多个Watcher同时触发时,ZooKeeper会按照注册顺序依次发送通知。
通过理解Watcher机制,开发人员可以更好地利用ZooKeeper来实现分布式系统中的实时数据变更和协调操作。Watcher机制提供了一种高效且可靠的方式,使得分布式系统能够实时响应节点变化,保持数据的一致性。
四、安装和基本操作
ZooKeeper提供了一组命令行工具(CLI)来与ZooKeeper集群进行交互。以下是一些常见的ZooKeeper命令:
- connect:
- 连接到ZooKeeper集群。
- 语法:
connect <host:port>
- ls:
- 列出指定路径下的子节点。
- 语法:
ls <path>
- create:
- 创建一个节点。
- 语法:
create <path> <data>
- get:
- 获取指定节点的数据。
- 语法:
get <path>
- set:
- 设置指定节点的数据。
- 语法:
set <path> <data>
- delete:
- 删除指定节点。
- 语法:
delete <path>
- stat:
- 获取指定节点的详细信息,包括数据版本、ACL权限等。
- 语法:
stat <path>
- getAcl:
- 获取指定节点的ACL(访问控制列表)权限信息。
- 语法:
getAcl <path>
- setAcl:
- 设置指定节点的ACL权限信息。
- 语法:
setAcl <path> <acl>
- quit/exit:
- 退出ZooKeeper命令行工具。
- 语法:
quit或exit
这些命令可以通过在ZooKeeper命令行工具中输入相应的命令来执行。使用这些命令,可以查看和操作ZooKeeper集群中的节点和数据,管理ACL权限,以及执行其他与ZooKeeper相关的操作。
五、java操作
1、基础api
当使用Java客户端连接ZooKeeper时,您可以使用Maven来管理项目依赖。下面是使用Maven连接ZooKeeper的基本步骤:
1、创建Maven项目:首先,创建一个新的Maven项目。您可以使用任何IDE或命令行工具来执行此操作。
2、添加Maven依赖:在您的Maven项目的pom.xml文件中,添加以下依赖项来引入ZooKeeper客户端库:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.1</version>
</dependency>
在上述代码中,我们指定了ZooKeeper客户端库的groupId为org.apache.zookeeper,artifactId为zookeeper,并指定了所需的版本号。您可以根据需要更新版本号。
3、测试用例
public class ZookeeperTest {
ZooKeeper zooKeeper;
@Before
public void createZk(){
// 定义连接参数
String connectString = "127.0.0.1:2181";
// 定义超时时间
int timeout = 10000;
try {
// new MyWatcher() 默认的watcher
zooKeeper = new ZooKeeper(connectString,timeout,new MyWatcher());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void testCreatePNode(){
try {
String result = zooKeeper.create("/ydlclass", "hello".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("result = " + result);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
} finally {
try {
if(zooKeeper != null){
zooKeeper.close();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Test
public void testDeletePNode(){
try {
// version: cas mysql 乐观锁, 也可以无视版本号 -1
zooKeeper.delete("/ydlclass",-1);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
} finally {
try {
if(zooKeeper != null){
zooKeeper.close();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Test
public void testExistsPNode(){
try {
// version: cas mysql 乐观锁, 也可以无视版本号 -1
Stat stat = zooKeeper.exists("/ydlclass", null);
zooKeeper.setData("/ydlclass","hi".getBytes(),-1);
// 当前节点的数据版本
int version = stat.getVersion();
System.out.println("version = " + version);
// 当前节点的acl数据版本
int aversion = stat.getAversion();
System.out.println("aversion = " + aversion);
// 当前子节点数据的版本
int cversion = stat.getCversion();
System.out.println("cversion = " + cversion);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
} finally {
try {
if(zooKeeper != null){
zooKeeper.close();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注: (1)version :当前结点的数据内容版本号;
(2)cversion:当前数据子节点的版本号;
(3)aversion:当前数据节点ACL变更的版本号;
2、watcher机制
watcher机制的本质是向zookeeper注册关心的事件,然后在本地存储钩子函数,当事件发生后调用钩子函数,如下图:
概念
- Zookeeper提供了数据的发布/订阅功能。多个订阅者可监听某一特定主题对象(节点)。当主题对象发生改变(数据内容改变,被删除等),会实时通知所有订阅者。该机制在被订阅对象发生变化时,会异步通知客户端,因此客户端不必在注册监听后轮询阻塞。
- Watcher机制实际上与观察者模式类似,也可看作观察者模式在分布式场景中给的一种应用。
特性
| 特性 | 说明 |
|---|---|
| 一次性 | Watcher是一次性的,一旦触发,就会被移除,再次使用需要重新注册 |
| 轻量级 | WatcherEvent是最小的通信单元,结构上只包含连接状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体情况 |
| 时效性 | watcher只有在当前session彻底时效时才会无效,若在session有效期内重新连接成功,则watcher依然存在 |
ZooKeeper中的读取操作getData、exist、getChildren 等都可以使用指定参数为节点设置监听。
Zookeeper监听有三个关键点:
- 客户端对该节点注册监听,也就是客户端对该节点进行订阅。
- 该节点发生改变,触发某一事件后,客户端会收到一个通知。可以执行相应回调执行相应动作。
- 注册的监听只会生效一次,要想继续生效,就要在回调的方法中继续注册监听。
java api中 有三个方法可以注册监听,getData、exist、getChildren。
监听器:
- 接听器接口Watcher,我们可以实现该接口实现自定义的监听器注册到节点上。
- 事件类型可以分为连接事件状态类型和节点事件类型。
- 事件类型:由Watcher.Event.EventType枚举维护。
主要有5种类型:
- NodeCreated:节点被创建时触发。
- NodeDeleted:节点被删除时触发。
- NodeDataChanged:节点数据被修改时触发。
- NodeChildrenChanged:子节点被创建或者删除时触发。
- NONE: 该状态就是连接状态事件类型。前面四种是关于节点的事件,这种是连接的事件,具体由Watcher.Event.KeeperState枚举维护。
注册事件的方式与节点事件的关系:
| 方式 | NodeCreated | NodeDeleted | NodeDataChanged | NodeChildrenChanged |
|---|---|---|---|---|
| exist | 可监控 | 可监控 | 可监控 | 不可监控 |
| getData | 不可监控 | 可监控 | 可监控 | 不可监控 |
| getChildren | 不可监控 | 可监控 | 不可监控 | 可监控 |
以上表格是对设置监听的方法对相应的事件是否可监控到。比如exist方法对节点删除事件不可监控,假如用该方法注册监听的话,节点删除时并不会触发事件回调。
连接事件类型,是指客户端连接时会触发的事件,由Watcher.Event.KeeperState枚举维护,主要有四个类型:
- SyncConnected :客户端与服务器正常连接时触发的事件。
- Disconnected :客户端与服务器断开连接时触发的事件。
- AuthFailed:身份认证失败时触发的事件
- Expired :客户端会话Session超时时触发的事件。
我们举例并进行如下测试:
public class MyWatcher implements Watcher {
@Override
public void process(WatchedEvent event) {
// 判断事件类型,连接类型的事件
if(event.getType() == Event.EventType.None){
if(event.getState() == Event.KeeperState.SyncConnected){
System.out.println("zookeeper连接成功");
} else if (event.getState() == Event.KeeperState.AuthFailed){
System.out.println("zookeeper认证失败");
} else if (event.getState() == Event.KeeperState.Disconnected){
System.out.println("zookeeper断开连接");
}
} else if (event.getType() == Event.EventType.NodeCreated){
System.out.println(event.getPath() + "被创建了");
} else if (event.getType() == Event.EventType.NodeDeleted){
System.out.println(event.getPath() + "被删除了了");
} else if (event.getType() == Event.EventType.NodeDataChanged){
System.out.println(event.getPath() + "节点的数据改变了");
} else if (event.getType() == Event.EventType.NodeChildrenChanged){
System.out.println(event.getPath() + "子节点发生了变化");
}
}
}
测试 用例:
@Test
public void testWatcher(){
try {
// 以下三个方法可以注册watcher,可以直接new一个新的watcher,
// 也可以使用true来选定默认的watcher
zooKeeper.exists("/ydlclass", true);
// zooKeeper.getChildren();
// zooKeeper.getData();
while(true){
Thread.sleep(1000);
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
} finally {
try {
if(zooKeeper != null){
zooKeeper.close();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
六、集群安装
zookeeper的运行模式有单机模式,伪集群模式,集群模式三种。单个Zookeeper节点是会存在单点故障的我们搭建一个zk集群。Zookeeper节点部署越多,服务的可靠性越高,通常建议部署奇数个节点,因为zookeeper集群是以宕机个数过半才会让整个集群宕机的。
zookeeper的集群模式下,节点分为leader和follower两种状态,leader负责所有的写操作,follower负责相关的读操作。
1、准备环境
| 主机名 | 系统 | IP地址 |
|---|---|---|
| linux-node1 | CentOS release 8 | 192.168.126.129 |
| linux-node2 | CentOS release 8 | 192.168.126.132 |
| linux-node2 | CentOS release 8 | 192.168.126.133 |
2、JDK安装
JDK下载地址:www.oracle.com/technetwork…
rpm -ivh jdk-8u101-linux-x64.rpm
3、Zookeeper安装
Zookeeper链接:zookeeper.apache.org/
wget http://mirrors.cnnic.cn/apache/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz -P /usr/local/src/
tar zxvf zookeeper-3.4.8.tar.gz -C /opt
cd /opt && mv zookeeper-3.4.8 zookeeper
cd zookeeper
cp conf/zoo_sample.cfg conf/zoo.cfg
把zookeeper加入到环境变量
echo -e "# append zk_env\nexport PATH=$PATH:/opt/zookeeper/bin" >> /etc/profile
Zookeeper运行需要java环境,需要安装jdk,注:每台服务器上面都需要安装zookeeper、jdk,建议本地下载好需要的安装包然后上传到服务器上面,服务器上面下载速度太慢。
修改zookeeper的配置文件,构建集群的基础配置:
dataLogDir=/opt/zookeeper/logs
dataDir=/opt/zookeeper/data
server.1= 192.168.126.129:2888:3888
server.2= 192.168.126.132:2888:3888
server.3= 192.168.126.133:2888:3888
server.1中的1指代第几个节点,2888端口用来辅助这个服务器与集群中的leader服务器做交换信息的端口,3888端口是在leader挂掉时专门用来进行选举leader所用的端口。
创建日志和持久化目录:
mkdir -p /opt/zookeeper/{logs,data}
创建ServerID标识
除了修改zoo.cfg配置文件外,zookeeper集群模式下还要配置一个myid文件,这个文件需要放在dataDir目录下。
这个文件里面有一个数据就是服务器编号,在zoo.cfg文件中配置的dataDir路径中创建myid文件。如在server.1服务器上面创建myid文件,就将他的值设置为1。以此类推2,3
echo "1" > /opt/zookeeper/data/myid
**小节:**ZooKeeper集群的启动过程包括以下步骤:
- 准备配置文件:
- 创建每个ZooKeeper节点的配置文件。配置文件包含节点的唯一标识(例如,ID)、监听地址和端口、数据目录等信息。
- 配置文件还包括集群的信息,包括每个节点的连接信息和选举算法。
- 启动ZooKeeper节点:
- 在每个节点上运行ZooKeeper进程。可以使用命令行或脚本来启动每个节点。
- 在启动命令中指定节点的配置文件,确保每个节点使用正确的配置。
- 节点启动顺序:
- 为了保证ZooKeeper集群的正常运行,节点的启动顺序很重要。通常,建议先启动节点ID较小的节点,然后依次启动其他节点。
- 启动过程中,每个节点会尝试连接到其他节点并加入集群。
- 初始化数据目录:
- 在节点启动时,ZooKeeper会检查配置文件中指定的数据目录。
- 如果数据目录不存在,ZooKeeper会自动创建,并初始化节点的数据存储结构。
- 数据同步和选举:
- 在节点加入集群后,ZooKeeper会开始进行数据同步和选举过程。
- 数据同步是指将节点的数据状态与其他节点进行同步,以确保集群中的数据一致性。
- 选举过程是为了选出一个节点作为Leader(领导者),负责协调和处理客户端请求。
- Leader选举完成:
- 一旦Leader选举完成,集群中的节点将分为Leader和Follower两类。
- Leader节点负责处理所有写操作和协调集群的状态,而Follower节点负责复制和同步Leader节点的数据。
- 集群就绪:
- 当ZooKeeper集群中的节点完成数据同步和选举后,整个集群将进入就绪状态。
- 客户端可以通过连接到任意一个节点来与集群进行通信和操作。
通过以上步骤,ZooKeeper集群中的节点将完成启动过程并开始提供服务。在启动过程中,注意配置文件的正确性和节点启动顺序的保持,这样可以确保集群的正常运行和数据的一致性。