Zookeeper 基础学习
Zookeeper 基础学习
Zookeeper 官网: http://zookeeper.apache.org/ 注:以下操作在CentOS7环境操作。
Zookeeper 是 Apache 的一个分布式服务框架,是 Apache Hadoop 的一个子项目。官方文档上这么解释 Zookeeper,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。简单来说 zookeeper=文件系统+监听通知机制。
学前须知
Zoopeeker数据模型
ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二进制序列。并且。每个节点还可以拥有 N 个子节点,最上层是根节点以“/”来代表。每个数据节点在 ZooKeeper 中被称为 Znode,它是 ZooKeeper 中数据的最小单元。并且,每个 znode 都有一个唯一的路径标识。
Znode
在 Zookeeper 中,znode 是一个跟 Unix 文件系统路径相似的节点,可以向节点存储数据或者获取数据。Zookeeper 底层是一套数据结构。这个存储结构是一个树形结构,其上的每一个节点,我们称之为 “Znode”。
其中每一个 Znode 默认能够存储 1MB 的数据(Zookeeper主要是协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据)
Znode 节点的 4 中不同的类型:
PERSISTENT-持久化目录节点
客户端与 zookeeper 断开连接后,该节点依旧存在。
PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与 zookeeper 断开连接后,该节点依旧存在,只是 Zookeeper 给该节点名称进行顺序编号。
EPHEMERAL-临时目录节点
客户端与 zookeeper 断开连接后,该节点被删除。
EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与 zookeeper 断开连接后,该节点被删除,只是 Zookeeper 给该节点名称进行顺序编号。
监听通知机制
ZooKeeper使用了一种类似于观察者设计模式的方法来实现实时的数据变化通知机制。当客户端注册监听它关心的目录节点(ZNode)时,如果该目录节点发生了变化(如数据改变、被删除、子节点增加或删除),ZooKeeper会通过Watcher机制通知客户端
零、环境安装
(一)、安装单机版本
首先准备一个Linux操作系统,可以是云服务器或者是虚拟机。
1、上传Zookeeper对应版本的压缩包并解压
# 解压
tar -zxf apache-zookeeper-3.6.0-bin.tar.gz
# 如果以前使用过其它版本可以查看并停止
ps -ef | grep zookeeper
kill -9 (id)
2、目录结构
bin:放置运行脚本和工具脚本,
conf:zookeeper 默认读取配置的目录,里面会有默认的配置文件
docs:zookeeper 相关的文档
lib:zookeeper 核心的 jar
logs(默认没有这个目录):zookeeper 日志
3、配置Zookeeper
Zookeeper 在启动时默认的去 conf 目录下查找一个名称为 zoo.cfg 的配置文件。在 zookeeper 应用目录中有子目录 conf。其中有配置文件模板:zoo_sample.cfg。zookeeper 应用中的配置文件为 conf/zoo.cfg。
# 使用拷贝指令先生成一个配置文件
cp zoo_sample.cfg zoo.cfg
然后:修改配置文件 zoo.cfg 中的 dataDir参数,指定了ZooKeeper实例的数据目录,也就是ZooKeeper保存其数据(如事务日志和快照文件)。
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/tmp/zookeeper # 修改为你创建的目录 mkdir xxx
# 如
dataDir=/www/apache-zookeeper-3.6.0-bin/data
其余参数:
clientPort:指定客户端连接ZooKeeper服务端口。
tickTime:ZooKeeper的基本时间单位,以毫秒为单位。
initLimit:初始化阶段允许的最大tick数。
syncLimit:同步阶段允许的最大tick数。
4、启动ZooKeeper
这里可以使用默认启动的方式,同时也可以通过指定配置文件路径来启动。(该文件在bin目录下)
# 默认的会去 conf 目录下加载 zoo.cfg 配置文件
./zkServer.sh start
# 指定加载配置文件
./zkServer.sh start 配置文件的路径。
启动成功后logs目录就会出现
5、停止ZooKeeper
./zkServer.sh stop
6、查看ZooKeeper状态
./zkServer.sh status
# 显示信息:
/usr/local/jdk1.8.0_144/bin/java
ZooKeeper JMX enabled by default
Using config: /www/apache-zookeeper-3.6.0-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone
7、连接Zookeeper
注意配置文件zoo.cfg中是否修改对应的客户端连接端口号
# 连接成功输出:
Welcome to ZooKeeper!
...
[zk: localhost:2181(CONNECTED) 0] [root@hcss-ecs-369d bin]#
连接方式一
当然呢,下面的文件也在bin里面,建议不知道的小伙伴可以自己找找,你只需要清楚是运行当前目录下的sh所以找到就行了_
# 默认连接地址为本机地址,默认连接端口为 2181
./zkCli.sh
连接方式二
# 连接指定 IP 地址与端口
./zkCli.sh -server ip:port
查看一下(后续会有指令介绍):
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]
# Ctrl + C 退出
(二)、集群安装
1、基础了解
首先Zookeeper 集群中的角色主要有以下三类,这表示我们想要构建一个集群是需要这些角色的即三个:
角色 | 说明 |
---|---|
Leader(领导者) | 为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。 |
Follower(跟随者) | 为客户端提供读服务,如果是写服务则转发给 Leader。参与选举过程中的投票。 |
Observer(观察者) | 为客户端提供读服务,如果是写服务则转发给 Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。 |
2、集群配置
(1)、首先
停止开始启动的单体zookeeper,可以使用linux命令进行检查,参考:1、上传Zookeeper对应版本的压缩包并解压,或者确定启动的情况下可以使用停止启动的指令:./zkServer.sh stop
(2)、其次
使用 3 个 Zookeeper 应用搭建一个伪集群(即通过不同部署在一台服务器上,使用不同端口区分)。客户端监听端口分别为:2181、2182、2183。投票选举端口分别为 2881/3881、2882/3882、2883/3883。
这里我们将已经解压好的Zookeeper,拷贝三份放在一个新的目录中,如集群目录(自己mkdir一个):zookeeperCluster
# 将 apache-zookeeper-3.6.0-bin 目录 拷贝到对应的 ./zookeeperCluster 目录中
cp -r apache-zookeeper-3.6.0-bin ./zookeeperCluster
为了利于集群标识:
# 这里我们通过移动的方式,将其改名为第一个 01,后续在拷贝 02 03
mv apache-zookeeper-3.6.0-bin zookeeper01
注意:如果是根据前面的单体拷贝过来的,已经创建的data还在,当然你可以删除里面的内容,如果直接看的集群,就需要创建这个文件,并且构建配置文件 zoo.cfg
这里和单体类似,我们先准备一个data目录存储 (参考:3、配置Zookeeper)
mkdir data # 我直接就在其根目录下创建
# 拷贝准备配置文件
cp zoo_sample.cfg zoo.cfg
# 拷贝另外两个
cp -r zookeeper01 zookeeper02
cp -r zookeeper01 zookeeper03
(3)、再次
修改三个服务对应的配置文件中的 dataDir 属性地址为对应的路径:
# 01
dataDir=/www/zookeeperCluster/zookeeper01/data
# 02
dataDir=/www/zookeeperCluster/zookeeper02/data
# 03
dataDir=/www/zookeeperCluster/zookeeper03/data
(4)、再次
在 Zookeeper 集群中,每个节点需要一个唯一标识。这个唯一标识要求是自然数。且唯一标识保存位置是:数据缓存目录(dataDir=/usr/local/zookeeper/data)的 myid 文件中。
其中“数据缓存目录”为配置文件 zoo.cfg 中的配置参数在 data 目录中创建文件 myid :
# 创建文件
touch myid
# 为应用提供唯一标识。本环境中使用 1、2、3 作为每个节点的唯一标识。
vi myid
简化方式为: echo [唯一标识] >> myid。 echo 命令为回声命令,系统会将命令发送的数据返回。 '>>'为定位,代表系统回声数据指定发送到什么位置。 此命令代表系统回声数据发送到 myid 文件中。 如果没有文件则创建文件。
# 参考命令,即将内容 1 写入 >> 后的文件,如果文件不存在则先创建文件
echo 1 >> ./zookeeper01/data/myid
echo 2 >> ./zookeeper02/data/myid
echo 3 >> ./zookeeper03/data/myid
# 使用 cat 确定一下
cat ./zookeeper01/data/myid
(6)、最后
修改配置文件 zoo.cfg - 设置监听客户端、投票、选举端口
# 进行编辑
vim zoo.cfg
写入下面内容(注意:server.[这里的数字就是你对应myid中设置的值]):
注意:下面的服务所在的ip如果你是一台服务器部署就用localhost这样不用设置安全组也可以测试是否成功,当然如果你使用的是对应的公网ip那就需要设置对应的安全组。
#服务端口根据应用做对应修改,zk01-2181,zk02-2182,zk03-2183
clientPort=2181
# 以下每一个服务都要加,相互之间要能找到对方,毕竟没有使用nginx来找,只放在领导者,万一领导者挂了=_=
server.1=服务所在的ip:2881:3881
server.2=服务所在的ip:2882:3882
server.3=服务所在的ip:2883:3883
检查以下,没有问题就可以启动了:
# 启动
zookeeper01/bin/zkServer.sh start
zookeeper02/bin/zkServer.sh start
zookeeper03/bin/zkServer.sh start
# 查看状态
zookeeper01/bin/zkServer.sh status
# 如果出现这个,看看是不是配置了公网ip没有设置安全组
Error contacting service. It is probably not running.
可以看到下面效果就已经ok了:
(7)Observer 哪去了?
Observer 则需要进步修改配置,当然前提是你的版本 >= ZooKeeper3.3 。
# 修改配置文件
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883:observer # 这个表示你要将这个服务作为 观察者
此时:
(8)、关于客户端连接
前面提及了单体中有两种连接方式,其中第二种方式发挥作用了,可以使用任何节点中的客户端工具连接集群中的任何节点:
# 这里可以自行测试,比如使用第一个服务的客户端连接第二个服务
./zkCli.sh -server 对应集群中的一个服务的ip:对应端口
3、使用脚本简化集群启动和关闭
1、启动
选择一个合适的位置放置脚本,这里取决于文件相对目录
# 创建一个脚本文件
vim startall.sh
# 写入内容
zookeeper01/bin/zkServer.sh start
zookeeper02/bin/zkServer.sh start
zookeeper03/bin/zkServer.sh start
# 赋予权限,ll查看权限,并验证
# 在 Linux 中可以使用 chmod 命令为文件授权。
# 777 表示为文件分配可读,可写,可执行权限
chmod 777 startall.sh
# 使用ll命令查看,文件变绿一般就是可执行了
2、关闭
关闭同理
# 创建一个脚本文件
vim stopall.sh
# 写入内容
zookeeper01/bin/zkServer.sh stop
zookeeper02/bin/zkServer.sh stop
zookeeper03/bin/zkServer.sh stop
# 赋予权限,ll查看权限,并验证
# 在 Linux 中可以使用 chmod 命令为文件授权。
# 777 表示为文件分配可读,可写,可执行权限
chmod 777 stopall.sh
# 使用ll命令查看,文件变绿一般就是可执行了
# 验证是否关闭
ps -ef | grep zookeeper
一、常用命令
官方文档地址:https://zookeeper.apache.org/doc/r3.6.4/zookeeperStarted.html
(一)、ls命令(查看节点)
比如 ls / 就是查看对应根路径,与Linux有出入,不能直接ls。
# 必须指定对应的节点路径
ls /path
(二)、create命令(创建节点)
如: create /test 123 创建一个/test 节点,节点携带数据信息 123。注意临时节点也不是立即删除,会有一定过程。
# 使用 create 命令创建一个新的 Znode。
create [-e] [-s] /path [data]
create -e /node mydata # 表示创建一个临时目录节点
create /node mydata # 表示创建一个持久化目录节点
create -e -s /node mydata # 临时顺序编号
create -s /node mydata # 持久顺序编号
其中 -e 指定是否为临时即:EPHEMERAL;-s指定是否设置 SEQUENTIAL 顺序编号,参照Znode节点的四种不同类型。
(三)、get命令(获取节点数据或信息)
# 携带 -s 参数表示你要查看对应节点的详细信息,否则就是查看数据
get [-s] /path
信息参考:
对应属性 | 对应含义 |
---|---|
第一行 | 存放的数据 |
cZxid | 创建时 zxid(znode 每次改变时递增的事务 id) |
ctime | 创建时间戳 |
mZxid | 最近一次更近的 zxid |
mtime | 最近一次更新的时间戳 |
pZxid | 子节点的 zxid |
cversion | 子节点更新次数 |
dataversion | 节点数据更新次数 |
aclVersion | 节点 ACL(授权信息)的更新次数 |
ephemeralOwner | 如果该节点为 ephemeral 节点(临时,生命周期与 session 一样),ephemeralOwner 值表示与该节点绑定的 session id。 如果该节点不是ephemeral 节点, ephemeralOwner 值为 0。 |
dataLength | 节点数据字节数 |
numChildren | 子节点数量 |
(四)、set命令(添加或修改Znode中的值)
set /path [data]
(五)、delete命令(删除节点)
delete /path
二、使用JavaApi操作Zookeeper
这里提及一下,可以使用 ZooInspector 工具连接你已经运行好的Zookeeper,查看是否成功后,再使用Java代码连接,考虑到使用云服务器有时会忘记设置安全组。
(一)、创建Java maven工程:zookeeperdemo
添加对应版本的依赖:
<dependencies>
<!-- 添加对应版本的zookeeper依赖 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
创建一个测试类进行测试:
public class ZnodeDemo implements Watcher {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//创建Zookeeper连接
/**
* 参数说明:
* 第一个 表示连接地址,TODO 如果启动的是集群使用逗号隔开
* 第二个 表示连接超时时间
* 第三个 一个事件回调类,通过实现对应接口创建
*/
ZooKeeper zooKeeper = new ZooKeeper("你的IP:端口", 150000, new ZnodeDemo());
//TODO 创建Znode
// String path = zooKeeper.create("/bjsxt/tesx", "mytest".getBytes(),
// ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
//
// System.out.println("对应创建节点的路径是:" + path);
//TODO 获取Znode
// byte[] data = zooKeeper.getData("/bjsxt/tesx0000000000", new ZnodeDemo(), new Stat());
// System.out.println(new String(data));
//TODO 获取所有子节点的数据
// List<String> children = zooKeeper.getChildren("/", new ZnodeDemo());
// for (String path : children) { //注意这里只是获取对应节点位置的一个路径名
//// System.out.println(path);
// byte[] data = zooKeeper.getData("/" + path, new ZnodeDemo(), new Stat());
// System.out.println("对应 /" + path + "的数据为:" + new String(data));
// }
//TODO 设置node的值
// Stat stat = zooKeeper.setData("/bjsxt/tesx0000000000", "update".getBytes(), -1);//-1 表示匹配所有版本
// System.out.println(stat); //对应的属性信息的值
//TODO 删除node的值
zooKeeper.delete("/bjsxt/tesx0000000000", -1);
}
/**
* 事件通知回调
* @param event
*/
@Override
public void process(WatchedEvent event) {
//获取连接事件
if(event.getState() == Event.KeeperState.SyncConnected) {
System.out.println("连接成功");
}
}
}
三、基于RMI实现远程调用
(一)、简介
RMI(Remote Method Invocation) 远程方法调用。RMI 是从 JDK1.2 推出的功能,它可以实现在一个 Java 应用中可以像调用本地方法一样调用另一个服务器中 Java 应用(JVM)中的内容。
RMI 是 Java 语言的远程调用,无法实现跨语言。
对应基本流程图:
Registry(注册表)是放置所有服务器对象的命名空间。 每次服务端创建一个对象时,它都会使用 bind()或 rebind()方法注册该对象。 这些是使用称为绑定名称的唯一名称注册的。要调用远程对象,客户端需要该对象的引用。即通过服务端绑定的名称从注册表中获取
对象(lookup()方法)。
(二)、RMI的基本API
1、Remote 接口
java.rmi.Remote 定义了此接口为远程调用接口。如果接口被外部调用,需要继承此接口。
2、RemoteException 类
java.rmi.RemoteException 继承了 Remote 接口的接口,如果方法是允许被远程调用的,需要抛出此异常。
3、UnicastRemoteObject 类
java.rmi.server.UnicastRemoteObject 此类实现了 Remote 接口和 Serializable 接口。自定义接口对应的实现类除了实现自定义接口(就是你自定义继承Remote的接口)还需要继承此类。
4、LocateRegistry 类
java.rmi.registry.LocateRegistry 可以通过 LocateRegistry 在本机上创建 Registry,通过特定的端口就可以访问这个Registry。也就是这个类可以创建一个注册表。
5、Naming 类
java.rmi.Naming Naming 定义了发布内容可访问 RMI 名称。也是通过 Naming 获取到指定的远程方法。简单来说就是可以将类与注册表绑定或者从注册表中获取对应的类。
(三)、创建对应的模块进行测试
这里为了方便先创建一个空项目,然后加模块。创建后注意检查模块使用的JDK版本,同时检查对应编码Encoding,和Java Complier,这两个在Settings中搜索就可以找到,经常修改本地JDK版本需要注意一下是否和自己当前的JDK对应
1、rmiserver 模块 —— maven工程
没有pom依赖,没有yaml。这里模块启动后,程序就会等待外部调用。
对外暴露接口(一定要注意这个接口你所放的包,要和消费者一致):
public interface DemoService extends Remote {
/**
* 对应允许被远程调用的方法
* @param str
* @return
* @throws RemoteException 必须要抛出
*/
String demo(String str) throws RemoteException;
}
对应实现类:
public class DemoServiceImpl extends UnicastRemoteObject implements DemoService {
/**
* 对应实现的类中构造器抛出了这个异常
* @throws RemoteException
*/
public DemoServiceImpl() throws RemoteException {
}
/**
* 对应被远程调用的方法的实现
* @param str
* @return
* @throws RemoteException
*/
@Override
public String demo(String str) throws RemoteException {
return "Hello RMI "+ str;
}
}
对应启动类(路径中的 demoService 是这个类的唯一标识):
public class DemoServer {
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
// 将对象实例化
DemoService demoService = new DemoServiceImpl();
// 创建本地注册表
LocateRegistry.createRegistry(8888);
// 将对象绑定到注册表中
Naming.bind("rmi://localhost:8888/demoService", demoService);
}
}
2、rmiclient 模块 —— maven工程
没有pom依赖,没有yaml。
对应需要调用的接口:
特别注意:这里的这个接口的包名和接口名必须和前面模块对外暴露的接口一致,因为 Java 通过其类或接口的包路径和类名来唯一标识他们,如果你这里的包名不一致,那么就无法和前面对应。
public interface DemoService extends Remote {
/**
* 对应允许被远程调用的方法
* @param str
* @return
* @throws RemoteException 必须要抛出
*/
String demo(String str) throws RemoteException;
}
对应的启动类:
public class ClientDemo {
public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
//从注册表中获取对应类
DemoService demoService = (DemoService) Naming.lookup("rmi://localhost:8888/demoService");
//方法调用
String result = demoService.demo("hello");
System.out.println(result);
}
}
这里关于 为什么接口的包名和接口名必须和前面模块对外暴露的接口一致,多提一下,我们通过Java语言使用一些如MySQL数据库产品时,一般都会先依赖对应的jar包,这些jar使其实现了Java定义的规范后开发的,而我们通过这个具体的实现就可以通过Java使用对应产品。如果我们需要做一些允许范围内的改动,就需要实现其对外暴露的接口。而这时我们所使用的接口一定要是别人开发的那个接口即要保证是同一个,同理这里你需要使用上一个模块对外暴露的接口,所以对应包名和类或接口名一定要一致。
当然对于接触过微服务开发的人来说那就似曾相识了,因为我们在使用类似Open Feign时,一般都会将一个服务需要对外暴露的接口写在对应的API模块中,然后本服务依赖这个API模块,并且需要调用的服务也会依赖这个API模块。所以可以想到其实这里我们使用了maven工程,我们也可以直接来client中依赖server模块,这样我们在client中就可以直接使用对外暴露的接口,但是这样也引入了一些不必要的东西,所以你可以想到_,其实这里我们也可以将需要对外暴露的接口提取为一个模块然后两个模块都依赖其即可。当然对于这个测试项目显然没有太大必要,如果你的需要使用且预计未来体量较大就可以这样设计。
3、运行效果:
服务端等待:
客户端调用:
四、使用Zookeeper作为注册中心记录服务地址
通过前面使用RMI实现远程调用我们可以发现,RMI有一个地方就是找到对应远程调用的地址,那么其实我们使用Zookeeper中对应的node来存储这个地址,那么我们就可以通过其找到。说到这里估计有人就会觉得这不是脱了裤子放屁吗?简直多次一举,我都已经可以使用RMI实现这个功能了,为什么还要加入Zookeeper这么麻烦。
这里我们需要注意,首先有些时候思考一些设计不能只考虑开始编码时的简易,还需要考虑后期的维护性。假设思考一下,我们使用RMI来进行RPC,无论是服务的提供者还是消费者,我们都需要获取对应类在注册表中的唯一地址,对于服务的提供者,就是它将对应的地址制定好的。而一般来说服务的消费者数量是远远大于服务的提供者的。当然将注册表地址基地址可以配置文件化这个是比较容易想到的。但是这只是使用修改基地址变动时有好处,并没有处理 基地址 + 唯一标识 拼接后所导致的路径变动需要所有消费者都修改的问题。
如果后续因某种不可控因素,我们服务提供者的基地址或者唯一标识发生改变,那么我们将要去所有的消费者中或许是配置文件,或者是硬编码全部修改。但是如果我们将其存储在 Zookeeper 中,那么只需要通过指定的抽象定义获取地址的名称,即我们当时设定这个名称可以获取这个对外暴露接口的类从而实现远程调用的地址的抽象名称,如Znode:/userserver/gerUserInfo,这种抽象名称定义的唯一路径我们基本不会有任何改动,只要服务的提供者和消费者约定好即可,这样就可以通过修改这个Znode节点中的值来一步到位,修改好所有消费者的对应需要修改的路径。(此见解受限于个人,合理理解,批判看待)
(一)、创建对应模块
两个模块需要导入的pom依赖,使用前面提到的单体或者基本集群的zookeeper部署版本3.6.0,虚拟机部署注意开防火墙,云服务器部署注意自己匹配安全组。
<dependencies>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
zookeeperserver 模块 —— maven工程
虽然直接拷贝前面模块代码,然后增加Zookeeper逻辑即可,但是这里还是重新命名一下。
定义对应的接口:
public interface UserService extends Remote {
/**
* 用户服务对外提供获取用户基本信息
* @param str
* @return
* @throws RemoteException 必须要抛出
*/
String getUserInfo(String str) throws RemoteException;
}
定义对应的实现类:
public class UserServiceImpl extends UnicastRemoteObject implements UserService {
/**
* 对应实现的类中构造器抛出了这个异常
* @throws RemoteException
*/
public UserServiceImpl() throws RemoteException {
}
@Override
public String getUserInfo(String str) throws RemoteException {
return "对应信息是:" + str;
}
}
定义对应的启动类
public class UserServer implements Watcher {
public static void main(String[] args) throws IOException, AlreadyBoundException, KeeperException, InterruptedException {
// 实例化对象
UserService usersService = new UserServiceImpl();
// 创建本地注册表
LocateRegistry.createRegistry(8888);
String url ="rmi://localhost:8888/userInfo"; //可以是配置文件管理
// 将对象绑定到注册表中
Naming.bind(url, usersService);
// TODO 以下为zookeeper交互,注意替换你自己的端口IP
// 将 url 信息放到 Zookeeper 的节点中
ZooKeeper zooKeeper = new ZooKeeper("你的IP:你的端口",150000, new UserServer());
// 创建 Znode,这里节点名称就是一个约定好的名称,合理设计可以减少后续修改
//TODO 在创建子节点之前,确保所有父节点都已存在。 /userserver 可以先使用客户端连接工具创建一下
zooKeeper.create("/userserver/gerUserInfo", url.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("服务注册到Zookeeper成功");
}
/**
* 事件通知回调
* @param event
*/
@Override
public void process(WatchedEvent event) {
//获取连接事件
if(event.getState() == Event.KeeperState.SyncConnected) {
System.out.println("服务端连接 Zookeeper 成功");
}
}
}
运行效果:
zookeeperclient 模块 —— maven工程
定义接口(注意包名 + 类名要和前面服务提供者对应一致,解释请看前面RMI模块):
public interface UserService extends Remote {
/**
* 用户服务对外提供获取用户基本信息
* @param str
* @return
* @throws RemoteException 必须要抛出
*/
String getUserInfo(String str) throws RemoteException;
}
定义启动类:
public class ClientDemo implements Watcher {
public static void main(String[] args) throws IOException, NotBoundException, KeeperException, InterruptedException {
// 连接Zookeeper,注意替换你自己的端口IP
ZooKeeper zooKeeper = new ZooKeeper("你的IP:你的端口",150000, new ClientDemo());
// 从 Znode 中获取url
byte[] data = zooKeeper.getData("/userserver/gerUserInfo", new ClientDemo(), new Stat());
String url = new String(data);
//从注册表中获取对应类
UserService userService = (UserService) Naming.lookup(url);
//方法调用
String result = userService.getUserInfo("姓名:xxx,性别:xxx");
System.out.println(result);
}
/**
* 事件通知回调
* @param event
*/
@Override
public void process(WatchedEvent event) {
//获取连接事件
if(event.getState() == Event.KeeperState.SyncConnected) {
System.out.println("客户端连接 Zookeeper 成功");
}
}
}
运行效果: