传送门:
如何实现“跨虚拟机”的调用?
它就是 RMI(Remote Method Invocation,远程方法调用)。例如,服务 A 在 JVM1 中运行,服务 B 在 JVM2 中运行,服务 A 与 服务 B 可相互进行远程调用,就像调用本地方法一样,这就是 RMI。在分布式系统中,我们使用 RMI技术可轻松将服务提供者(Service Provider)与 服务消费者(Service Consumer)进行分离,充分体现组件之间的弱耦合,系统架构更易于扩展。
我们先从通过一个最简单的 RMI 服务与调用示例,快速掌握 RMI 的使用方法,然后指RMI 的局限性,最后笔者对此问题提供了一种简单的解决方案,即使用 ZooKeeper 轻松解决 RMI 调用过程中所涉及的问题。
RMI细节流程如下:
再实现RMI服务前,我们搭建框架如下:
发布RMI服务,我们只需要做三件事:
在common包下,新建HelloService类,接口选择:Interface接口。
RMI接口实际上还是一个普通的Java接口,只是RMI接口必须继承java.rmi.Remote , 此 外 , 每个RMI接口的方法必须声明抛出一个java.rmi.RemoteException 异常,就像下面这样:
package com.bjsxt.remote.common;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**编写普通的java接口,要求继承remote接口
*定义的方法需要抛出RemoteException异常
*/
public interface HelloService extends Remote {
public String sayHello(String name) throws RemoteException;
}
在server包下,新建HelloServiceImpl类,接口选择:Class接口。
实现以上的 HelloService 是一件非常简单的事情,但需要注意的是,我们必须让实现类继承 java.rmi.server.UnicastRemoteObject 类,此外,必须提供一个构造器,并且构造器必须抛出 java.rmi.RemoteException 异常。我们既然使用 JVM 提供的这套 RMI 框架,那么就必须按照这个要求来实现,否则是无法成功发布 RMI 服务的,一 句话:我们得按规矩出牌!
package com.bjsxt.remote.server;
import com.bjsxt.remote.common.HelloService;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/** 实现类除了实现HelloService以外,还需要继承UnicastRemoteObject类;
* 添加一个构造方法,需要抛出RemoteException异常
*/
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
protected HelloServiceImpl() throws RemoteException {
}
@Override
public String sayHello(String name) throws RemoteException {
return "Hello"+name;
}
}
在server包下,新建RMIServer类,接口选择:Class接口。
发布 RMI 服务,我们需要告诉 JNDI 三个基本信息:
域名或 IP 地址(host)、
端口号(port)、
服务名(service),
它们构成了 RMI 协议的 URL(或称为“RMI 地址”):
rmi://:/
如果我们是在本地发布 RMI 服务,那么 host 就是“localhost”。此外,RMI 默认的 port 是“1099”,我们也可以自行设置 port 的值(只要不与其它端口冲突即可)。 service 实际上是一个基于同一 host 与 port 下唯一的服务名,我们不妨使用 Java完全类名(包名.类名)来表示,这样也比较容易保证 RMI 地址的唯一性。
对于我们的示例而言,RMI 地址为:
rmi://localhost:1099/com.bjsxt.remote.server.HelloServiceImpl
我们只需简单提供一个 main() 方法就能发布 RMI 服务,就像下面这样:
package com.bjsxt.remote.server;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class RMIServer {
//定义main方法来发布RMI服务
public static void main(String[] args) throws RemoteException, MalformedURLException {
int port = 1099; //定义端口
//定义URL
String url = "rmi://localhost:1099/com.bjsxt.remote.server.HelloServiceImpl";
//绑定端口号
LocateRegistry.createRegistry(port);
//注册具体的服务
Naming.rebind(url,new HelloServiceImpl());
}
}
需要注意的是,我们通过 LocateRegistry.createRegistry() 方法在 JNDI 中创建一个注册表,只需提供一个 RMI 端口号即可。此外,通过 Naming.rebind() 方法绑定 RMI 地址与 RMI 服务实现类,这里使用了 rebind() 方法,它相当于先后调用Naming 的 unbind() 与 bind() 方法,只是使用 rebind() 方法来得更加痛快而已,所以我们选择了它。
运行这个 main() 方法,RMI 服务就会自动发布,剩下要做的就是写一个 RMI 客户端来调用已发布的 RMI 服务。
在client包下,新建RMIClient类,接口选择:Class接口。
同样我们也使用一个 main() 方法来调用 RMI 服务,相比发布而言,调用会更加简单,我们只需要知道两个东西:
数行代码就能调用刚才发布的 RMI 服务,就像下面这样:
package com.bjsxt.remote.client;
import com.bjsxt.remote.common.HelloService;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class RMIClient {
public static void main(String[] args) throws MalformedURLException, NotBoundException, RemoteException {
//定义URL
String url = "rmi://localhost:1099/com.bjsxt.remote.server.HelloServiceImpl";
// 发现服务
HelloService hs = (HelloService) Naming.lookup(url);
//调用远程服务
String result = hs.sayHello("Jack");
//打印result
System.out.println(result);
}
}
运行时,先运行RMIServer.java(服务器端),再运行RMIClient(客户端)。在控制台中看到“Hello Jack”输出,就表明 RMI 调用成功。
我们在客户端远程调用了服务器端的服务,这就是RMI。
借助 JNDI (Java Naming and Directory Interface)这个所谓的命名与目录服务,我们成功地发布并调用了 RMI 服务。实际上,JNDI 就是一个注册表,服务端将服务对象放入到注册表中,客户端从注册表中获取服务对象。在服务端我们发布了 RMI 服务,并在 JNDI 中进行了注册,此时就在服务端创建了一个 Skeleton(骨架),当客户端第一次成功连接 JNDI 并获取远程服务对象后,立马就在本地创建了一个 Stub(存根),远程通信实际上是通过 Skeleton 与 Stub 来完成的,数据是基于 TCP/IP 协议,在“传输层”上发送的。毋庸置疑,理论上 RMI 一定比 WebService 要快,毕竟 WebService 是基于 HTTP 的,而 HTTP 所携带的数据是通过“应用层”来传输的,传输层较应用层更为底层,越底层越快。
既然 RMI 比 WebService 快,使用起来也方便,那么为什么我们有时候还要用 WebService 呢?
其实原因很简单,WebService 可以实现跨语言系统之间的调用,而 RMI 只能实现 Java系统之间的调用。也就是说,RMI 的跨平台性不如 WebService 好,假如我们的系统都是用 Java 开发的,那么当然首选就是 RMI 服务了。
RMI服务主要有以下两点局限性:
在一般的情况下,Java 默认的序列化方式确实已经足以满足我们的要求了,如果性能方面不是问题的话,我们需要解决的实际上是第二点,也就是说,让系统具备 HA(High Availability,高可用性)。
备注:
https://www.cnblogs.com/wlzjdm/p/7856356.html(JNDI理解)
JNDI是一个命名目录接口,提供与外界的一个访问关系,只要相关应用、设备能提供服务,那么我们就可以通过JNDI来连接处理。
OSI模型七层网络结构
使用Java原生的RMI的例子中,我们的服务端存在单点故障。如果服务器端down掉,我们在使用客户端请求时会无响应。接下来,我们使用ZooKeeper实现高可用的RMI服务。
要想解决 RMI 服务的高可用性问题,我们需要利用 ZooKeeper 充当一个服务注册表(Service Registry),让多个服务提供者(Service Provider)形成一个集群(每一个服务提供者在zookeeper树形结构上注册一个临时节点,临时节点里面存放服务提供者中RMI的url,客户端可以访问对应的节点来访问具体的服务提供者),让服务消费者(Service Consumer)通过服务注册表获取具体的服务访问地址(也就是 RMI 服务地址)去访问具体的服务提供者。如下图所示:
详细实现原理图:
执行流程:
需要注意的是,服务注册表并不是 Load Balancer(负载均衡器),提供的不是“反向代理”服务,而是“服务注册”与“心跳检测”功能。利用服务注册表来注册 RMI 地址,这个很好理解,那么“心跳检测”又如何理解呢?说白了就是通过服务中心定时向各个服务提供者发送一个请求(实际上建立的是一个 Socket长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,只会从还“活 着”的服务提供者中选出一个做为当前的服务提供者 。也许服务中心可能会出现单点故障,如果服务注册表都坏掉了,整个系统也就瘫痪了。看来要想实现这个架构,必须保证服务中心也具备高可用性。ZooKeeper 正好能够满足我们上面提到的所有需求。
使用 ZooKeeper 的临时性 ZNode 来存放服务提供者的 RMI 地址,一旦与服务提供者的 Session 中断,会自动清除相应的 ZNode。让服务消费者去监听这些 ZNode,一旦发现 ZNode 的数据(RMI 地址)有变化,就会重新获取一份有效数据的拷贝。 ZooKeeper 与生俱来的集群能力(例如:数据同步与领导选举特性),可以确保服务注册表的高可用性。
在common包下,定义Constant接口:
package com.bjsxt.remote.common;
public interface Constant {
//定义zk连接地址
String ZK_CONNECTION_STRING = "192.168.236.32.2181,192.168.236.33.2181,192.168.236.34.2181";
//session失效时间50s
int ZK_SESSION_TIMEOUT = 5000;
//服务注册表中使用的父节点
String ZK_REGISTRY_PATH = "/registry";
//子节点
String ZK_PROVIDER_PATH = ZK_REGISTRY_PATH + "/provide";
}
在server包下,定义ServiceProvider类:
需要编写一个 ServiceProvider 类,来发布 RMI 服务,并将 RMI 地址注册到ZooKeeper 中(实际存放在 ZNode 上)。
package com.bjsxt.remote.server;
import com.bjsxt.remote.common.Constant;
import org.apache.zookeeper.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.util.concurrent.CountDownLatch;
public class ServiceProvider {
public static final Logger LOGGER = (Logger) LoggerFactory.getLogger(ServiceProvider.class);
// 用于等待SysConnected事件触发后继续执行当前线程
private CountDownLatch latch = new CountDownLatch(1);
//发布RMI服务并注册RMI地址到ZooKeeper中
public void publish(Remote remote, String host, int port) {
//发布RMI服务并返回RMI地址(url地址)
String url = publishService(remote, host, port);
if (url != null) {
//连接ZooKeeper服务器并获取ZooKeeper对象
ZooKeeper zk = connectServer();
if (zk != null) {
// 创建ZNode并将RMI地址放入ZNode上
createNode(zk, url);
}
}
}
//发布RMI服务
private String publishService(Remote remote, String host, int port) {
String url = null;
try {
//remote.getClass().getName()即为包名.类名
url = String.format("rmi://%s:%d/%s", host, port, remote.getClass().getName());
//绑定端口
LocateRegistry.createRegistry(port);
//发布具体服务
Naming.rebind(url, remote);
LOGGER.debug("publish rmi service(url:{})", url);
} catch (RemoteException | MalformedURLException e) {
LOGGER.error("", e);
}
return url;
}
//连接ZooKeeper服务器
private ZooKeeper connectServer() {
ZooKeeper zk = null;
try {
zk = new ZooKeeper(Constant.ZK_CONNECTION_STRING, Constant.ZK_SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
//如果连接建立
if (event.getState() == Event.KeeperState.SyncConnected) {
//唤醒当前正在执行的线程
latch.countDown();
}
}
});
//使当前线程处于等待状态
latch.await();
} catch (IOException | InterruptedException e) {
LOGGER.error("", e);
}
return zk;
}
//创建ZNode
private void createNode(ZooKeeper zk, String url) {
byte[] data = url.getBytes();
//创建一个临时性且有序的ZNode
try {
String path = zk.create(Constant.ZK_PROVIDER_PATH, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
LOGGER.debug("create zookeeper node ({} => {}", path, url);
} catch (KeeperException | InterruptedException e) {
LOGGER.error("", e);
}
}
}
注意:我们首先需要使用 ZooKeeper 的客户端工具创建一个持久性 ZNode,名为“/registry”,该节点是不存放任何数据的,可使用如下命令:
[zk: localhost:2181(CONNECTED) 0] ls /
[zk02, zk01, zookeeper]
[zk: localhost:2181(CONNECTED) 1] create /registry null
Created /registry
[zk: localhost:2181(CONNECTED) 2] ls /
[registry, zk02, zk01, zookeeper]
在sever包下,编写Server.java类
package com.bjsxt.remote.server;
import com.bjsxt.remote.common.HelloService;
public class Server {
public static void main(String[] args) throws Exception {
//当前RMI服务器的ip和端口
String host = "192.168.236.1";
//端口:11214
int port = Integer.parseInt("11214");
//定义ServiceProvider类,
ServiceProvider provider = new ServiceProvider();
//调用原生RMI的实现类
HelloService helloService = new HelloServiceImpl();
//发布服务:包括建立zk连接、创建节点等
provider.publish(helloService,host,port);
//休眠最大值,保持服务器程序运行,持续的对外提供服务
Thread.sleep(Long.MAX_VALUE);
}
}
以port:11214端口运行一次,然后在以port:11215端口运行一次,最后关闭11215的运行,在Xshell中表现如下:
[zk: localhost:2181(CONNECTED) 21] ls /registry
[]
[zk: localhost:2181(CONNECTED) 22] ls /registry
[provide0000000016]
[zk: localhost:2181(CONNECTED) 23] get /registry/provide0000000016
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
cZxid = 0xc00000011
ctime = Wed Dec 22 12:36:29 CST 2021
mZxid = 0xc00000011
mtime = Wed Dec 22 12:36:29 CST 2021
pZxid = 0xc00000011
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x27de05d709f0002
dataLength = 66
numChildren = 0
[zk: localhost:2181(CONNECTED) 24] ls /registry
[provide0000000018, provide0000000016]
[zk: localhost:2181(CONNECTED) 25] get /registry/provide0000000018
rmi://192.168.236.1:11215/com.bjsxt.remote.server.HelloServiceImpl
cZxid = 0xc00000016
ctime = Wed Dec 22 12:37:17 CST 2021
mZxid = 0xc00000016
mtime = Wed Dec 22 12:37:17 CST 2021
pZxid = 0xc00000016
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x37de05d7a5a0001
dataLength = 66
numChildren = 0
[zk: localhost:2181(CONNECTED) 26] ls /registry
[provide0000000016]
在client包下,创建ServiceConsumer类。
服务消费者(Client)需要在创建的时候连接ZooKeeper , 同时监听/registry 节点的NodeChildrenChanged事件,也就是说,一旦该节点的子节点有变化,就需要重新获取最新的子节点。这里提到的子节点,就是存放服务提供者发布的 RMI 地址。需要强调的是,这些子节点都是临时性的,当服务提供者与 ZooKeeper 服务注册表的 Session中断后,该临时性节会被自动删除。
package com.bjsxt.remote.client;
import com.bjsxt.remote.common.Constant;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.MalformedURLException;
import java.rmi.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
public class ServiceConsumer {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceConsumer.class);
//用于等待SyncConnetcted事件触发后继续执行当前进程
private CountDownLatch latch = new CountDownLatch(1);
//定义一个volatile成员变量,用来保存最新的RMI地址(开率到该变量或许会被其他线程所修改,一旦修改后,该变量的值会影响到所有线程)
private volatile List<String> urlList = new ArrayList<>();
//构造器
public ServiceConsumer(){
//连接ZooKeeper服务器并获取ZooKeeper对象
ZooKeeper zk = connectServer();
if (zk != null){
//观察/registry节点的所有子节点并更新urlList成员变量
watchNode(zk);
}
}
//查找RMI服务
public <T extends Remote> T lookup(){
T service = null;
int size = urlList.size();
if (size > 0){
String url;
if (size == 1){
//若urlList中只有一个元素,则直接获取该元素
url = urlList.get(0);
LOGGER.debug("using only url : {}",url);
}else{
//若urlList中存在多个元素,则随机获取一个元素
url = urlList.get(ThreadLocalRandom.current().nextInt(size));
LOGGER.debug("using random url : {}",url);
}
System.out.println(url);
//从JNDI中寻找RMI服务
service = lookupService(url);
}
return service;
}
//连接ZooKeeper服务器
private ZooKeeper connectServer() {
ZooKeeper zk = null;
try {
zk = new ZooKeeper(Constant.ZK_CONNECTION_STRING, Constant.ZK_SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
//如果连接建立
if (event.getState() == Event.KeeperState.SyncConnected){
//唤醒当前正在执行的进程
latch.countDown();
}
}
});
//使当前线程处于等待状态
latch.await();
} catch (IOException | InterruptedException e) {
LOGGER.error("",e);
}
return zk;
}
//观察/registry节点下所有子节点是否有变化,如果有变化,则更新最新的RMI地址
private void watchNode(final ZooKeeper zk) {
try {
//节点列表(节点名称)
List<String> nodeList = zk.getChildren(Constant.ZK_REGISTRY_PATH, new Watcher() {
@Override
public void process(WatchedEvent event) {
//如果节点发生改变
if (event.getType() == Event.EventType.NodeChildrenChanged) {
//若子节点有变化,则重新调用该方法(为了获取最新子节点中的数据),实现多次监听
watchNode(zk);
}
}
});
//用于存放/registry所有子节点中的数据
List<String> dataList = new ArrayList<>();
//遍历节点名称列表,取出节点中的节点列表存放到dataList中
for (String node : nodeList) {
//获取/registry的子节点中的数据
byte[] data = zk.getData(Constant.ZK_REGISTRY_PATH + "/" + node, false, null);
dataList.add(new String(data));
}
LOGGER.debug("node data: {}", dataList);
//更新最新的RMI地址
urlList = dataList;
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
LOGGER.error("", e);
}
}
//在JNDI中寻找RMI远程服务对象
@SuppressWarnings("unchecked")
private <T> T lookupService(String url){
T remote = null;
try {
//Naming.lookup:返回与指定 name 关联的远程对象的引用
remote = (T) Naming.lookup(url);
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException | RemoteException e) {
if (e instanceof ConnectException){
//若连接中断,则使用urlList中第一个RMI地址来查找(这是一种简单的重试方式,确保不会抛出异常)
LOGGER.error("ConnectException -> url : {}",url);
if (urlList.size() != 0) {
url = urlList.get(0);
return lookupService(url);
}
}
LOGGER.error("",e);
}
return remote;
}
}
在client包下,创建Server类。
通过调用 ServiceConsumer 的 lookup() 方法来查找 RMI 远程服务对象。我们使用一个“死循环”来模拟每隔 3 秒钟调用一次远程方法。
package com.bjsxt.remote.server;
import com.bjsxt.remote.common.HelloService;
public class Server {
public static void main(String[] args) throws Exception {
// if (args.length != 2) {
// System.err.println("please using command : java Server ");
// System.exit(-1);
// }
// String host = args[0];
// int port = Integer.parseInt(args[1]);
// ServiceProvider provider = new ServiceProvider();
// HelloService helloService = new HelloServiceImpl();
//
// provider.publish(helloService,host,port);
//
// Thread.sleep(Long.MAX_VALUE);
//当前RMI服务器的ip和端口
String host = "192.168.236.1";
//端口:11214
int port = Integer.parseInt("11215");
//定义ServiceProvider类,
ServiceProvider provider = new ServiceProvider();
//调用原生RMI的实现类
HelloService helloService = new HelloServiceImpl();
//发布服务:包括建立zk连接、创建节点等
provider.publish(helloService,host,port);
//休眠最大值,保持服务器程序运行,持续的对外提供服务
Thread.sleep(Long.MAX_VALUE);
}
}
根据以下步骤验证 RMI 服务的高可用性:
运行两个 Server 程序,一定要确保 port 是不同的。
运行一个 Client 程序。
停止其中一个 Server 程序,并观察 Client 控制台的变化(停止一个 Server 不会导致 Client 端调用失败)。
重新启动刚才关闭的 Server 程序,继续观察 Client 控制台变化(新启动的 Server会加入候选)。
先后停止所有的 Server 程序,还是观察 Client 控制台变化(Client 会重试连接,多次连接失败后,自动关闭)。
2021-12-22 14:14:10,904 [myid:] - INFO [main:Environment@100] - Client environment:zookeeper.version=3.4.6-1569965, built on 02/20/2014 09:09 GMT
2021-12-22 14:14:10,906 [myid:] - INFO [main:Environment@100] - Client environment:host.name=LAPTOP-G7CRBRST
2021-12-22 14:14:10,907 [myid:] - INFO [main:Environment@100] - Client environment:java.version=1.8.0_311
2021-12-22 14:14:10,908 [myid:] - INFO [main:Environment@100] - Client environment:java.vendor=Oracle Corporation
2021-12-22 14:14:10,908 [myid:] - INFO [main:Environment@100] - Client environment:java.home=D:\Java\jdk1.8.0_311\jre
2021-12-22 14:14:10,908 [myid:] - INFO [main:Environment@100] - Client environment:java.class.path=D:\java\jdk1.8.0_311\jre\lib\charsets.jar;D:\java\jdk1.8.0_311\jre\lib\deploy.jar;D:\java\jdk1.8.0_311\jre\lib\ext\access-bridge-64.jar;D:\java\jdk1.8.0_311\jre\lib\ext\cldrdata.jar;D:\java\jdk1.8.0_311\jre\lib\ext\dnsns.jar;D:\java\jdk1.8.0_311\jre\lib\ext\jaccess.jar;D:\java\jdk1.8.0_311\jre\lib\ext\jfxrt.jar;D:\java\jdk1.8.0_311\jre\lib\ext\localedata.jar;D:\java\jdk1.8.0_311\jre\lib\ext\nashorn.jar;D:\java\jdk1.8.0_311\jre\lib\ext\sunec.jar;D:\java\jdk1.8.0_311\jre\lib\ext\sunjce_provider.jar;D:\java\jdk1.8.0_311\jre\lib\ext\sunmscapi.jar;D:\java\jdk1.8.0_311\jre\lib\ext\sunpkcs11.jar;D:\java\jdk1.8.0_311\jre\lib\ext\zipfs.jar;D:\java\jdk1.8.0_311\jre\lib\javaws.jar;D:\java\jdk1.8.0_311\jre\lib\jce.jar;D:\java\jdk1.8.0_311\jre\lib\jfr.jar;D:\java\jdk1.8.0_311\jre\lib\jfxswt.jar;D:\java\jdk1.8.0_311\jre\lib\jsse.jar;D:\java\jdk1.8.0_311\jre\lib\management-agent.jar;D:\java\jdk1.8.0_311\jre\lib\plugin.jar;D:\java\jdk1.8.0_311\jre\lib\resources.jar;D:\java\jdk1.8.0_311\jre\lib\rt.jar;D:\IdeaProjects\baizhan\zookeeper_demo\out\production\zookeeper_demo;D:\IdeaProjects\baizhan\zookeeper_demo\lib\netty-3.7.0.Final.jar;D:\IdeaProjects\baizhan\zookeeper_demo\lib\jline-0.9.94.jar;D:\IdeaProjects\baizhan\zookeeper_demo\lib\log4j-1.2.16.jar;D:\IdeaProjects\baizhan\zookeeper_demo\lib\slf4j-api-1.6.1.jar;D:\IdeaProjects\baizhan\zookeeper_demo\lib\slf4j-log4j12-1.6.1.jar;D:\IdeaProjects\baizhan\zookeeper_demo\lib\zookeeper-3.4.6.jar;C:\Users\admin\.m2\repository\junit\junit\4.13.1\junit-4.13.1.jar;C:\Users\admin\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\softwares\IntelliJ IDEA 2021.3\lib\idea_rt.jar
2021-12-22 14:14:10,908 [myid:] - INFO [main:Environment@100] - Client environment:java.library.path=D:\Java\jdk1.8.0_311\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\lib\x64;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\bin;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\libnvvp;D:\softwares\anaconda3;D:\softwares\anaconda3\Scripts;D:\softwares\anaconda3\Library\bin;C:\windows\system32;C:\windows;C:\windows\System32\Wbem;C:\windows\System32\WindowsPowerShell\v1.0\;C:\windows\System32\OpenSSH\;C:\Program Files\MySQL\MySQL Server 8.0\bin;D:\MySQL\MySQL Server 5.5\bin;D:\softwares\anaconda3\graphviz\bin;D:\softwares\mysql5.6.39\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;D:\softwares\anaconda3\envs\newtouch;D:\softwares\anaconda3\envs\newtouch\Scripts;D:\softwares\anaconda3\envs\newtouch\Library\bin;C:\Program Files\Git\cmd;D:\softwares\nodejs\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\Program Files\NVIDIA Corporation\Nsight Compute 2019.1\;D:\java\jdk1.8.0_311\bin;D:\mysql\mysql56\bin;C:\windows;C:\windows\system32;C:\windows\system32\wbem;D:\softwares\CUDA10.0\bin;D:\softwares\CUDA10.0\libnvvp;D:\softwares\CUDA10.0\lib\x64;C:\windows\System32\Wbem;C:\windows\System32\WindowsPowerShell\v1.0\;C:\windows\System32\OpenSSH\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\Program Files\MySQL\MySQL Server 8.0\bin;D:\MySQL\MySQL Server 5.5\bin;D:\softwares\anaconda3\graphviz\bin;D:\Git\cmd;D:\softwares\mysql5.6.39\bin;d:\softwares\anaconda3;d:\softwares\anaconda3\Library\mingw-w64\bin;d:\softwares\anaconda3\Library\usr\bin;d:\softwares\anaconda3\Library\bin;d:\softwares\anaconda3\Scripts;C:\Users\admin\anaconda3;C:\Users\admin\anaconda3\Library\mingw-w64\bin;C:\Users\admin\anaconda3\Library\usr\bin;C:\Users\admin\anaconda3\Library\bin;C:\Users\admin\anaconda3\Scripts;D:\python3.7\Scripts\;D:\python3.7\;C:\Users\admin\AppData\Local\Microsoft\WindowsApps;D:\办公\pycharm\PyCharm Community Edition 2020.1.1\bin;D:\software;C:\Users\admin\AppData\Roaming\npm;.
2021-12-22 14:14:10,908 [myid:] - INFO [main:Environment@100] - Client environment:java.io.tmpdir=C:\Users\admin\AppData\Local\Temp\
2021-12-22 14:14:10,908 [myid:] - INFO [main:Environment@100] - Client environment:java.compiler=<NA>
2021-12-22 14:14:10,908 [myid:] - INFO [main:Environment@100] - Client environment:os.name=Windows 10
2021-12-22 14:14:10,908 [myid:] - INFO [main:Environment@100] - Client environment:os.arch=amd64
2021-12-22 14:14:10,909 [myid:] - INFO [main:Environment@100] - Client environment:os.version=10.0
2021-12-22 14:14:10,909 [myid:] - INFO [main:Environment@100] - Client environment:user.name=admin
2021-12-22 14:14:10,909 [myid:] - INFO [main:Environment@100] - Client environment:user.home=C:\Users\admin
2021-12-22 14:14:10,909 [myid:] - INFO [main:Environment@100] - Client environment:user.dir=D:\IdeaProjects\baizhan\zookeeper_demo
2021-12-22 14:14:10,910 [myid:] - INFO [main:ZooKeeper@438] - Initiating client connection, connectString=192.168.236.32:2181,192.168.236.33:2181,192.168.236.34:2181 sessionTimeout=5000 watcher=com.bjsxt.remote.client.ServiceConsumer$1@27fa135a
2021-12-22 14:14:11,742 [myid:] - INFO [main-SendThread(192.168.236.32:2181):ClientCnxn$SendThread@975] - Opening socket connection to server 192.168.236.32/192.168.236.32:2181. Will not attempt to authenticate using SASL (unknown error)
2021-12-22 14:14:11,743 [myid:] - INFO [main-SendThread(192.168.236.32:2181):ClientCnxn$SendThread@852] - Socket connection established to 192.168.236.32/192.168.236.32:2181, initiating session
2021-12-22 14:14:11,750 [myid:] - INFO [main-SendThread(192.168.236.32:2181):ClientCnxn$SendThread@1235] - Session establishment complete on server 192.168.236.32/192.168.236.32:2181, sessionid = 0x17de05d709e0002, negotiated timeout = 5000
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11215/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11215/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11215/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11215/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11215/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
rmi://192.168.236.1:11214/com.bjsxt.remote.server.HelloServiceImpl
HelloJack
Exception in thread "main" java.lang.NullPointerException
at com.bjsxt.remote.client.Client.main(Client.java:14)
通过使用ZooKeeper实现了一个简单的RMI服务高可用性解决方案,通过ZooKeeper 注册所有服务提供者发布的 RMI 服务,让服务消费者监听 ZooKeeper的Znode,从而获取当前可用的 RMI 服务。