Zookeeper 实战
主要内容
Zookeeper 简介
Zookeeper 存储结构
监听通知
安装 Zookeeper
Zookeeper 常用命令
使用 Java API 操作 Zookeeper
Zookeeper 实战
学习目标
一、 Zookeeper 简介
1 什么是 Zookeeper
Zookeeper 官网: http://zookeeper.apache.org/
Zookeeper 是 Apache 的一个分布式服务框架,是 Apache Hadoop 的一个子项目。官方文档上这么解释 Zookeeper,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,
如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
简单来说 zookeeper=文件系统+监听通知机制。
二、 Zookeeper 存储结构
1 Znode
在 Zookeeper 中,znode 是一个跟 Unix 文件系统路径相似的节点,可以向节点存储数据或者获取数据。
Zookeeper 底层是一套数据结构。这个存储结构是一个树形结构,其上的每一个节点,我们称之为“znode”
Zookeeper 中的数据是按照“树”结构进行存储的。而且 znode 节点还分为 4 种不同的类型。
每一个 znode 默认能够存储 1MB 的数据(对于记录状态性质的数据来说,够了)可以使用 zkCli 命令,登录到 Zookeeper 上,并通过 ls、create、delete、get、set等命令操作这些 znode 节点。
2 Znode 节点类型
2.1 PERSISTENT-持久化目录节点
客户端与 zookeeper 断开连接后,该节点依旧存在。
2.2 PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与 zookeeper 断开连接后,该节点依旧存在,只是 Zookeeper 给该节点名称进行顺序编号。
2.3 EPHEMERAL-临时目录节点
客户端与 zookeeper 断开连接后,该节点被删除。
2.4 EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与 zookeeper 断开连接后,该节点被删除,只是 Zookeeper 给该节点名称进行顺序编号。
三、 监听通知机制
Zookeeper 是使用观察者设计模式来设计的。当客户端注册监听它关心的目录节点时,
当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,Zookeeper 会通知客户端。
四、 安装 zookeeper
官方资源包可在 zookeeper.apache.com 站点中下载。最新发布版本为:3.6.0。
安装很简单,解压就可以用,但需要配置
1 安装单机版
1.1 安装 Linux
1.2 安装 JDK
配置环境变量
export JAVA_HOME=/usr/local/jdk
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$JAVA_HOME/bin:$PATH
1.3 上传 Zookeeper
1.4解压 Zookeeper 压缩包
拷贝到/usr/local/ 目录下
1.5Zookeeper 目录结构
logs目录是启动之后才会有的,记录日志
1. bin:放置运行脚本和工具脚本,
2. conf:zookeeper 默认读取配置的目录,里面会有默认的配置文件
3. docs:zookeeper 相关的文档
4. lib:zookeeper 核心的 jar
5. logs:zookeeper 日志
1.6配置 Zookeeper
创建zookeeper运行时数据缓存目录
Zookeeper 在启动时默认的去 conf 目录下查找一个名称为 zoo.cfg 的配置文件。
在 zookeeper 应用目录中有子目录 conf。其中有配置文件模板:zoo_sample.cfg
cp zoo_sample.cfg zoo.cfg==(将 zoo_sample.cfg文件拷贝到当前目录下改名为zoo.cfg 因为zoo.cfg 是zookeeper默认去找的配置文件)==。zookeeper 应用中的配置文件为 conf/zoo.cfg。
修改 zoo.cfg配置文件
clientPort:监听客户端连接的端口号默认为2181
dataDir : 设置zookeeper运行时数据缓存目录位置
1.7启动 Zookeeper
默认加载配置文件:./zkServer.sh start:默认的会去 conf 目录下加载 zoo.cfg 配置文件。
指定加载配置文件:./zkServer.sh start 配置文件的路径。
启动成功
查看zookeeper当前状态 zkServer.sh status
1.8停止 Zookeeper
./zkServer.sh stop
1.9查看 Zookeeper 状态
./zkServer.sh status
关于运行BUG:
zookeeper启动之后会生成logs目录用于记录日志
1.10使用客户端连接单机版 Zookeeper
1.10.1 连接方式一
bin/zkCli.sh
默认连接地址为本机地址,默认连接端口为 2181
拒绝连接的原因是zookeeper还没有启动
启动zookeeper
连接成功
现在在zookeeper下有一个结点
按Ctrl+C 退出连接
1.10.2 连接方式二
bin/zkCli.sh -server ip:port
连接指定 IP 地址与端口
eg:
连接成功
2 安装集群版
2.1Zookeeper 集群说明
2.1.1 Zookeeper 集群中的角色
Zookeeper 集群中的角色主要有以下三类
在创建zookeeper集群之前所有的结点都是跟随者,但是一旦通过内部的投票选举算法选出一个领导者之后,除了领导者之外每一个跟随者都是一个zookeeper的结点,当跟随者结点的数据发生变化时会通知领导者,领导者再去通知其他的跟随者,从发生数据变化的结点中做数据同步
2.2集群安装
使用 3 个 Zookeeper 应用搭建一个伪集群(在一个Linux系统中搭建集群)。应用部署位置是:192.168.88.101。
客户端监听端口分别为:2181、2182、2183。投票选举端口分别为 2881/3881、2882/3882、2883/3883。
投票和选举用的是两个端口
创建存放zookeeper集群实例的目录
tar -zxf zookeeper-3.6.0.tar.gz
将解压后的 Zookeeper 应用目录重命名,便于管理
mv zookeeper-3.6.0 zookeeper01
2.2.1 提供数据缓存目录
在 zookeeper01 应用目录中,创建 data 目录,用于缓存应用运行数据
cd zookeeper01
mkdir data
2.2.2 复制应用
复制两份 Zookeeper 应用。用于模拟集群中的 3 个节点。此时复制的Zookeeper应用带data目录
cp -r zookeeper01 zookeeper02
cp -r zookeeper01 zookeeper03
现在相当于安装好了三个Zookeeper实例了
2.2.3 提供配置文件
在 zookeeper 应用目录中有子目录 conf。其中有配置文件模板:zoo_sample.cfg
cp zoo_sample.cfg zoo.cfg
zookeeper 应用中的配置文件为 conf/zoo.cfg。
2.2.4 修改配置文件 zoo.cfg - 设置数据缓存路径
dataDir 参数值为应用运行缓存数据保存目录。
Zookeeper01
Zookeeper02
Zookeeper03
2.2.5 提供应用唯一标识
***在 Zookeeper 集群中,每个节点需要一个唯一标识。***这个唯一标识要求是自然数。且唯一标识保存位置是:数据缓存目录(dataDir=/usr/local/zookeeper/data)的 myid (data目录中没有需要自己创建,且必须叫myid)文件中。其中“数据缓存目录”为配置文件 zoo.cfg 中的配置参数
在 data 目录中创建文件 myid : touch myid
为应用提供唯一标识。本环境中使用 1、2、3 作为每个节点的唯一标识。
vi myid
简化方式为: echo [唯一标识] >> myid。
echo 命令为回声命令,系统会将命令发送的数据返回。 '>>'为定位,代表系统回声数据指定发送到什么位置。 此命令代表系统回声数据发送到 myid 文件中。 如果没有文件则创建文件。
自动帮助创建myid文件
继续标识Zookeeper02和Zookeeper03
2.2.6 修改配置文件 zoo.cfg - 设置监听客户端、投票、选举端口
vim zoo.cfg
server.1标识定位Zookeeper01这个实例 server.X X的值取决于实例中myid文件里面的值
192.168.88.101 是实例所在主机的IP地址 后面跟的是投票和选举端口
clientPort=2181 #服务端口根据应用做对应修改,zk01-2181,zk02-2182,zk03-2183
server.1=192.168.88.101:2881:3881
server.2=192.168.88.101:2882:3882
server.3=192.168.88.101:2883:3883
修改三个Zookeeper实例的配置文件
Zookeeper01 客户端监听端口为 2181
Zookeeper02 客户端监听端口改为 2182
Zookeeper03 客户端监听端口改为 2183
测试启动三个Zookeeper实例 Zookeeper会自动创建集群
集群创建成功 查看各个结点的状态,领导者和跟随者 观察者会在两个跟随者之间选一个,看不见
2.3编写启动、关闭集群脚本
在 Linux 中可以使用 chmod 命令为文件授权。
chmod 777 文件名
777 表示为文件分配可读,可写,可执行权限。
2.3.1 启动 Zookeeper 集群脚本
zookeeper01/bin/zkServer.sh start
zookeeper02/bin/zkServer.sh start
zookeeper03/bin/zkServer.sh start
给startall.sh文件分配可执行权限
可读可写可执行
运行这个集中启动的文件 三个Zookeeper实例都运行了
2.3.2 关闭 Zookeeper 集群脚本
zookeeper01/bin/zkServer.sh stop
zookeeper02/bin/zkServer.sh stop
zookeeper03/bin/zkServer.sh stop
创建集中关闭的文件 shutdownall.sh
分配可读可写可执行权限
运行集中关闭的可执行文件 三个Zookeeper实例都被关闭了
2.4连接集群
可以使用任何节点中的客户端工具连接集群中的任何节点。
./zkCli.sh -server 192.168.88.101:2183
客户端工具是谁无所谓,不管在不在集群里面都可以连接集群
先启动再连接,用Zookeeper01连接Zookeeper03
启动
连接
连接成功
用单机Zookeeper连接集群Zookeeper中的结点
五、Zookeeper 常用命令
1 ls 命令
ls /path
使用 ls 命令查看 zookeeper 中的内容。在 ZooKeeper 控制台客户端中,没有默认列表功能,
必须指定要列表资源的位置。 如: ls / 或者 ls /path
必须给个路径
查看根下 根下有个Zookeeper的结点
查看Zookeeper下有哪些结点
查看config下有哪些结点 config下没有任何结点了
2 create 命令
create [-e] [-s] /path [data]
path:指定在哪个位置创建结点 data:创建结点时添加的数据
使用 create 命令创建一个新的 Znode。create [-e] [-s] path data - 创建节点,
如: create /test 123 创建一个/test 节点(不加任何参数默认为持久类型的结点在客户端断开连接时不会被删除),节点携带数据信息 123。
create -e /test 123 创建一个临时节点/test,携带数据为 123,
临时节点只在当前会话生命周期中有效,会话结束节点自动删除。
create -s /test 123 创建一个顺序节点/test,携带数据 123,
创建的顺序节点由 ZooKeeper 自动为节点增加后缀信息,如-/test00000001 等。-e 和-s 参数可以联合使用。
在根下创建结点bjsxt
先退出链接再链接,bjsxt这个结点还在 不给任何参数默认为持久类型的
-s 表示创建一个带序号的持久化结点 结点的值为123 0000000001为结点的序号
-e 表示创建一个临时结点 不给值
临时结点退出客户端连接之后就会被删除,不是立即删除需要等待几秒钟 temp结点已被删除
创建一个临时的带序号的结点
退出重新连接客户端之后临时结点被删除
3 get 命令
get [-s] /path
get 命令获取 Znode 中的数据。
get -s /path
-s 查看 Znode 详细信息
lzc:存放的数据
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:子节点数量
4 set 命令
set /path [data]
添加或修改 Znode 中的值
修改结点bjsxt的值为AAA
创建一个空结点,然后通过set命令给结点赋值
5 delete 命令
delete /path
删除 Znode。
test结点已被删除
六、 使用 Java API 操作 Zookeeper
1 创建 Znode
1.1创建项目
1.2修改 POM 文件添加依赖
该依赖为基于 Java 语言连接 Zookeeper 的客户端工具
依赖版本应和按照的Zookeeper版本保持一致
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
1.3创建 Znode 并添加数据
前四个常量含义依次为:
创建权限
对当前操作开放所有权限
读取权限
只对当前客户端开放所有权限
package com.bjsxt;
import org.apache.zookeeper.*;
import java.io.IOException;
/**
* 操作Zookeeper的Znode
*/
public class ZnodeDemo implements Watcher {
public static void main(String[] args) throws KeeperException, InterruptedException, IOException {
//创建连接Zookeeper对象 这个对象是有添加的依赖jar包提供的
/**
* param1:给定当前去链接Zookeeper的IP地址和端口,连接Zookeeper集群则用逗号分隔
* param2:客户端工具在链接Zookeeper的超时时间
* param3:Zookeeper提供的做事件通知的处理器,对Zookeeper做事件操作Zookeeper会把事件传递到代码中,需要实现Watcher接口
* 在Watcher接口下有process()方法,采用回调的方式将事件行为通知给代码
*/
ZooKeeper zooKeeper = new ZooKeeper("192.168.88.101:2181,192.168.88.101:2182,192.168.88.101:2183", 150000, new ZnodeDemo());
//创建一个Znode create方法的返回值是创建Znode的路径
/**
* param1:创建Znode的位置
* param2:为Znode添加的值,必须是字节数组类型
* param3:权限设定
* param4:创建Znode的类型(4种类型)
*/
String path = zooKeeper.create("/bjsxt/test", "lzc".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
System.out.println(path);
}
/**
* 事件通知回调方法
* @param watchedEvent
* 一旦有时间产生Zookeeper会调用process方法通知
*/
@Override
public void process(WatchedEvent watchedEvent) {
//获取链接事件
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
System.out.println("连接成功!");
}
}
}
创建成功 在bjsxt结点下创建有序子节点test
2 获取 Znode 中的数据
2.1获取指定节点中的数据
package com.bjsxt;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
/**
* 操作Zookeeper的Znode
*/
public class ZnodeDemo implements Watcher {
public static void main(String[] args) throws KeeperException, InterruptedException, IOException {
//创建连接Zookeeper对象 这个对象是有添加的依赖jar包提供的
/**
* param1:给定当前去链接Zookeeper的IP地址和端口,连接Zookeeper集群则用逗号分隔
* param2:客户端工具在链接Zookeeper的超时时间
* param3:Zookeeper提供的做事件通知的处理器,对Zookeeper做事件操作Zookeeper会把事件传递到代码中,需要实现Watcher接口
* 在Watcher接口下有process()方法,采用回调的方式将事件行为通知给代码
*/
ZooKeeper zooKeeper = new ZooKeeper("192.168.88.101:2181,192.168.88.101:2182,192.168.88.101:2183", 150000, new ZnodeDemo());
//创建一个Znode create方法的返回值是创建Znode的路径
/**
* param1:创建Znode的位置
* param2:为Znode添加的值,必须是字节数组类型
* param3:权限设定
* param4:创建Znode的类型(4种类型)
*/
/*String path = zooKeeper.create("/bjsxt/test", "lzc".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
System.out.println(path);*/
//获取指定结点的数据 返回一个byte[]类型的字节数组
/**
* param1:获取数据的结点路径
* param2:回调处理器
* param3:能够返回节点的饿统计信息
*/
byte[] data = zooKeeper.getData("/bjsxt/test0000000000", new ZnodeDemo(), new Stat());
//得先将字节数组转换成String类型做字符串转换才能打印
System.out.println(new String(data));
}
/**
* 事件通知回调方法
* @param watchedEvent
* 一旦有时间产生Zookeeper会调用process方法通知
*/
@Override
public void process(WatchedEvent watchedEvent) {
//获取链接事件
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
System.out.println("连接成功!");
}
}
}
2.2获取所有子节点中的数据
//获取指定结点中的所有子节点中的数据 返回结果为list list中存放的是根据当前结点下所有子节点的路径
/**
* param1:取哪个结点的子节点的路径
* param2:回调处理器
*/
List<String> list = zooKeeper.getChildren("/bjsxt", new ZnodeDemo());
//遍历子节点路径
for (String path : list) {
//取某个结点的值得给完整的路径所以得拼接上跟路径 stat也可以不给,给个null就行
byte[] data = zooKeeper.getData("/bjsxt/" + path, new ZnodeDemo(), null);
System.out.println(new String(data));
}
}
bjsxt下只有一个子节点
3 设置 Znode 中的值
/**
* param1:给定需要设置值的结点的路径
* param2:需要设置的值 需要一个byte[]类型数组
* param3:版本匹配 给 -1 表示忽略版本匹配任何版本都可以
*/
Stat stat = zooKeeper.setData("/bjsxt/test0000000000", "bjsxt".getBytes(), -1);
System.out.println(stat);
数据发生改变
4 删除 Znode
//删除Znode中的值
/**
* param1:需要删除结点的路径
* param2:版本匹配 给 -1 表示忽略版本匹配任何版本都可以
*/
zooKeeper.delete("/bjsxt/test0000000000",-1);
删除成功
package com.bjsxt;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.List;
/**
* 操作Zookeeper的Znode
*/
public class ZnodeDemo implements Watcher {
public static void main(String[] args) throws KeeperException, InterruptedException, IOException {
//创建连接Zookeeper对象 这个对象是有添加的依赖jar包提供的
/**
* param1:给定当前去链接Zookeeper的IP地址和端口,连接Zookeeper集群则用逗号分隔
* param2:客户端工具在链接Zookeeper的超时时间
* param3:Zookeeper提供的做事件通知的处理器,对Zookeeper做事件操作Zookeeper会把事件传递到代码中,需要实现Watcher接口
* 在Watcher接口下有process()方法,采用回调的方式将事件行为通知给代码
*/
ZooKeeper zooKeeper = new ZooKeeper("192.168.88.101:2181,192.168.88.101:2182,192.168.88.101:2183", 150000, new ZnodeDemo());
//创建一个Znode create方法的返回值是创建Znode的路径
/**
* param1:创建Znode的位置
* param2:为Znode添加的值,必须是字节数组类型
* param3:权限设定
* param4:创建Znode的类型(4种类型)
*/
/*String path = zooKeeper.create("/bjsxt/test", "lzc".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
System.out.println(path);*/
//获取指定结点的数据 返回一个byte[]类型的字节数组
/**
* param1:获取数据的结点路径
* param2:回调处理器
* param3:能够返回节点的饿统计信息
*/
/*byte[] data = zooKeeper.getData("/bjsxt/test0000000000", new ZnodeDemo(), new Stat());
//得先将字节数组转换成String类型做字符串转换才能打印
System.out.println(new String(data));*/
//获取指定结点中的所有子节点中的数据 返回结果为list list中存放的是根据当前结点下所有子节点的路径
/**
* param1:取哪个结点的子节点的路径
* param2:回调处理器
*/
/*List<String> list = zooKeeper.getChildren("/bjsxt", new ZnodeDemo());
//遍历子节点路径
for (String path : list) {
//取某个结点的值得给完整的路径所以得拼接上跟路径 stat也可以不给,给个null就行
byte[] data = zooKeeper.getData("/bjsxt/" + path, new ZnodeDemo(), null);
System.out.println(new String(data));
}*/
//设置Znode中的值 返回stat类型是当前结点详细信息的对象
/**
* param1:给定需要设置值的结点的路径
* param2:需要设置的值 需要一个byte[]类型数组
* param3:版本匹配 给 -1 表示忽略版本匹配任何版本都可以
*/
/*Stat stat = zooKeeper.setData("/bjsxt/test0000000000", "bjsxt".getBytes(), -1);
System.out.println(stat);*/
//删除Znode中的值
/**
* param1:需要删除结点的路径
* param2:版本匹配 给 -1 表示忽略版本匹配任何版本都可以
*/
zooKeeper.delete("/bjsxt/test0000000000",-1);
}
/**
* 事件通知回调方法
* @param watchedEvent
* 一旦有时间产生Zookeeper会调用process方法通知
*/
@Override
public void process(WatchedEvent watchedEvent) {
//获取链接事件
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
System.out.println("连接成功!");
}
}
}
七、 Zookeeper 实战
实战案例介绍:使用 Zookeeper 与 RMI 技术实现一个 RPC 框架。
RPC:RPC(Remote Procedure Call)远程过程调用。
1 基于 RMI 实现远程方法调用
1.1RMI 简 介
RMI(Remote Method Invocation) 远程方法调用。
RMI 是从 JDK1.2 推出的功能,它可以实现在一个 Java 应用中可以像调用本地方法一样 调用另一个服务器中 Java 应用(JVM)中的内容。
RMI 是 Java 语言的远程调用,无法实现跨语言。
1.2执行流程
Server:被调用者
Client:调用者
Registry:注册表
Server端允许外界应用通过Server端对象调用Server端的方法(对象的暴露),如果不暴露这个对象只能在当前Server端使用,暴露需要使用bind()或者rebind()方法把这个对象绑定到一个注册表中,注册表有Server端提供,注册表中放的是所有暴露的对象,Client如果想通过Server中的对象调用某一个方法Client需要拿到Server端的注册表,再通过lookup()方法从注册表中找到需要的对象,然后Client拿着找到的对象调用Server下的方法
Registry(注册表)是放置所有服务器对象的命名空间。 每次服务端创建一个对象时,它都会使用 bind()或 rebind()方法注册该对象。 这些是使用称为绑定名称的唯一名称注册的。
**要调用远程对象,客户端需要该对象的引用。**即通过服务端绑定的名称从注册表中获取对象(lookup()方法)。
1.3RMI 的 API 介绍
1.3.1 Remote 接口
java.rmi.Remote 定义了此接口为远程调用接口。如果接口被外部调用,需要继承此接口。
1.3.2 RemoteException 类
java.rmi.RemoteException
继承了 Remote 接口的接口,如果方法是允许被远程调用的,需要抛出此异常。
1.3.3 UnicastRemoteObject 类
java.rmi.server.UnicastRemoteObject
此类实现了 Remote 接口和 Serializable 接口。
自定义接口实现类除了实现自定义接口还需要继承此类。
1.3.4 LocateRegistry 类
java.rmi.registry.LocateRegistry
可以通过 LocateRegistry 在本机上创建 Registry,通过特定的端口就可以访问这个Registry。
1.3.5 Naming 类
java.rmi.Naming
Naming 定义了发布内容可访问 RMI 名称。也是通过 Naming 获取到指定的远程方法。
1.4创建 Server 端
1.4.1 创建项目
创建空项目
添加模块
创建maven类型工程
1.4.2 创建接口
package com.bjsxt.service;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 允许远程调用接口,该接口必须要实现Remote接口
* 允许被远程调用的方法必须要抛出RemoteException异常
*/
public interface DemoService extends Remote {
String demo(String str) throws RemoteException;
}
1.4.3 创建接口实现类
需要将构造方法的修饰类型由 protected改成public
package com.bjsxt.service.impl;
import com.bjsxt.service.DemoService;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 接口实现类必须要继承UnicastRemoteObject
* 会自动添加构造方法,需要修改为public
*/
public class DemoServiceImpl extends UnicastRemoteObject implements DemoService {
//UnicastRemoteObject下有三个构造方法保留一个即可
public DemoServiceImpl() throws RemoteException {
}
@Override
public String demo(String str) throws RemoteException {
return "Hello RMI"+str;
}
}
1.4.4 编写主方法
package com.bjsxt;
import com.bjsxt.service.DemoService;
import com.bjsxt.service.impl.DemoServiceImpl;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class DemoServer {
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
//将对象实例化
DemoService demoService = new DemoServiceImpl();
//创建本地注册表 指定访问该注册表的端口号为8888
LocateRegistry.createRegistry(8888);
//将对象绑定到注册表上 rmi:表示未来查找对象是通过rmi协议查找 localhost:当前server所在主机IP
//第一个demoService是作为被绑定对象的唯一标识,一般用被绑定对象名称
Naming.bind("rmi://localhost:8888/demoService",demoService);
}
}
启动之后程序处于阻塞状态,说明createRegistry在监听8888端口,等着外界调用demoServer对象
1.5创建 Client 端
1.5.1 创建项目
1.5.2 复制服务端接口
package com.bjsxt.service;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 允许远程调用接口,该接口必须要实现Remote接口
* 允许被远程调用的方法必须要抛出RemoteException异常
*/
public interface DemoService extends Remote {
String demo(String str) throws RemoteException;
}
1.5.3 创建主方法
package com.bjsxt;
import com.bjsxt.service.DemoService;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.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("北京尚学堂");
System.out.println(result);
}
}
2 使用 Zookeeper 作为注册中心实现 RPC
以Zookeeper作为注册中心,rmiServer在启动时除了要把地址注册到本地注册表中还要把地址放到Zookeeper的一个Znode里面存储,rmiClient在启动时去链接Zookeeper找到存放地址的结点通过getDate()方法获取地址的值,然后将获取的地址交给lookup()方法,lookup()再去链接本地注册表拿到对象然后通过对象调用rmiServer里面的方法
2.1创建服务端
2.1.1 创建项目
2.1.2 修改 POM 文件添加依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
2.1.3 创建接口
package com.bjsxt.service;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface UsersService extends Remote {
String findUsers(String str) throws RemoteException;
}
2.1.4 创建接口实现类
package com.bjsxt.service.impl;
import com.bjsxt.service.UsersService;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class UsersServiceImpl extends UnicastRemoteObject implements UsersService {
//只保留一个无参数的构造方法即可 将protect改为public
public UsersServiceImpl() throws RemoteException {
}
@Override
public String findUsers(String str) throws RemoteException {
return "Hello Zookeeper " + str;
}
}
2.1.5 编写主方法
package com.bjsxt;
import com.bjsxt.service.UsersService;
import com.bjsxt.service.impl.UsersServiceImpl;
import org.apache.zookeeper.*;
import java.io.IOException;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class serverDemo implements Watcher {
public static void main(String[] args) throws IOException, AlreadyBoundException, KeeperException, InterruptedException {
//实例化对外暴露的对象
UsersService usersService = new UsersServiceImpl();
//将对象绑定到本地的注册表中 创建一个本地的注册表监听8888端口
LocateRegistry.createRegistry(8888);
String url = "rmi://localhost:8888/user";
//url为绑定标识 完成绑定
Naming.bind(url, usersService);
//将url放到Zookeeper的结点中
ZooKeeper zooKeeper = new ZooKeeper("192.168.88.101:2181,192.168.88.101:2182,192.168.88.101:2183",150000,new serverDemo());
//创建结点 并且将url放到Znode中
zooKeeper.create("/bjsxt/service", url.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("服务发布成功!!!");
}
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
System.out.println("链接成功!!!");
}
}
}
2.2创建客户端
2.2.1 创建项目
2.2.2 修改 POM 文件添加依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
2.2.3 创建接口
package com.bjsxt.service;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface UsersService extends Remote {
String findUsers(String str) throws RemoteException;
}
2.2.4 编写主方法
package com.bjsxt;
import com.bjsxt.service.UsersService;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
public class ClientDemo implements Watcher {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException, NotBoundException {
//链接Zookeeper
ZooKeeper zooKeeper = new ZooKeeper("192.168.88.101:2181,192.168.88.101:2182,192.168.88.101:2183", 150000, new ClientDemo());
//将Zookeeper当成注册中心从来里面取出来的
byte[] bytes = zooKeeper.getData("/bjsxt/service", new ClientDemo(), null);
//将字节数组转换成String类型
String url = new String(bytes);
System.out.println(url);
//找对象
UsersService usersService = (UsersService) Naming.lookup(url);
//通过拿到的对象调用方法
String result = usersService.findUsers("百战程序员 Zookeeper作为注册中心");
System.out.println(result);
}
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
System.out.println("链接成功!!!");
}
}
}