05 ZooKeeper分布式RMI协调实战
文章目录
- 05 分布式RMI协调实战
- 1. Java原生RMI实现
- 1.1 发布RMI服务
- 1.1.1 定义一个RMI接口
- 1.1.2 编写RMI接口的实现类
- 1.1.3 通过JNDI发布RMI服务
- 1.2 调用RMI服务
- 1.3 RMI服务的局限性
- 2. 使用ZooKeeper提供高可用的RMI服务原理分析
- 3. 使用ZooKeeper实现RMI高可用代码剖析
- 3.1 通用接口
- 3.2 服务端
- 3.3 客户端
- 3.3 客户端下调用服务
- 3.4 使用方法
- 3.5 总结
- gitee地址:zookeeper分布式协调服务框架
传送门:
- ZooKeeper初探:https://blog.csdn.net/weixin_46649052/article/details/122004447
- ZooKeeper分布式集群安装:https://blog.csdn.net/weixin_46649052/article/details/122017216
- ZooKeeper底层原理剖析与命令实战:https://blog.csdn.net/weixin_46649052/article/details/122042265
- ZooKeeperAPI实战:https://blog.csdn.net/weixin_46649052/article/details/122059148
- ZooKeeper分布式RMI协调实战:https://blog.csdn.net/weixin_46649052/article/details/122085619
05 分布式RMI协调实战
如何实现“跨虚拟机”的调用?
它就是 RMI(Remote Method Invocation,远程方法调用)。例如,服务 A 在 JVM1 中运行,服务 B 在 JVM2 中运行,服务 A 与 服务 B 可相互进行远程调用,就像调用本地方法一样,这就是 RMI。在分布式系统中,我们使用 RMI技术可轻松将服务提供者(Service Provider)与 服务消费者(Service Consumer)进行分离,充分体现组件之间的弱耦合,系统架构更易于扩展。
我们先从通过一个最简单的 RMI 服务与调用示例,快速掌握 RMI 的使用方法,然后指RMI 的局限性,最后笔者对此问题提供了一种简单的解决方案,即使用 ZooKeeper 轻松解决 RMI 调用过程中所涉及的问题。
1. Java原生RMI实现
RMI细节流程如下:
再实现RMI服务前,我们搭建框架如下:
1.1 发布RMI服务
发布RMI服务,我们只需要做三件事:
- 定义一个RMI接口
- 编写RMI接口的实现类
- 通过JNDI发布RMI服务
1.1.1 定义一个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;
}
1.1.2 编写RMI接口的实现类
在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 {}@Overridepublic String sayHello(String name) throws RemoteException {return "Hello"+name;}
}
1.1.3 通过JNDI发布RMI服务
在server包下,新建RMIServer类,接口选择:Class接口。
发布 RMI 服务,我们需要告诉 JNDI 三个基本信息:
域名或 IP 地址(host)、
端口号(port)、
服务名(service),
它们构成了 RMI 协议的 URL(或称为“RMI 地址”):
rmi://<host>:<port>/<service>
如果我们是在本地发布 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; //定义端口//定义URLString 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 服务。
1.2 调用RMI服务
在client包下,新建RMIClient类,接口选择:Class接口。
同样我们也使用一个 main() 方法来调用 RMI 服务,相比发布而言,调用会更加简单,我们只需要知道两个东西:
- RMI 请求路径、
- RMI 接口(一定不需要 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 {//定义URLString url = "rmi://localhost:1099/com.bjsxt.remote.server.HelloServiceImpl";// 发现服务HelloService hs = (HelloService) Naming.lookup(url);//调用远程服务String result = hs.sayHello("Jack");//打印resultSystem.out.println(result);}
}
运行时,先运行RMIServer.java(服务器端),再运行RMIClient(客户端)。在控制台中看到“Hello Jack”输出,就表明 RMI 调用成功。
我们在客户端远程调用了服务器端的服务,这就是RMI。
1.3 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服务主要有以下两点局限性:
- RMI 使用了 Java 默认的序列化方式,对于性能要求比较高的系统,可能需要使用其它序列化方案来解决(例如:Protobuf)。
- RMI 服务在运行时难免会存在出故障,例如,如果 RMI 服务无法连接了,就会导致客户端无法响应的现象,存在类似于“单点故障”的问题。
在一般的情况下,Java 默认的序列化方式确实已经足以满足我们的要求了,如果性能方面不是问题的话,我们需要解决的实际上是第二点,也就是说,让系统具备 HA(High Availability,高可用性)。
备注:
https://www.cnblogs.com/wlzjdm/p/7856356.html(JNDI理解)
JNDI是一个命名目录接口,提供与外界的一个访问关系,只要相关应用、设备能提供服务,那么我们就可以通过JNDI来连接处理。
OSI模型七层网络结构
2. 使用ZooKeeper提供高可用的RMI服务原理分析
使用Java原生的RMI的例子中,我们的服务端存在单点故障。如果服务器端down掉,我们在使用客户端请求时会无响应。接下来,我们使用ZooKeeper实现高可用的RMI服务。
要想解决 RMI 服务的高可用性问题,我们需要利用 ZooKeeper 充当一个服务注册表(Service Registry),让多个服务提供者(Service Provider)形成一个集群(每一个服务提供者在zookeeper树形结构上注册一个临时节点,临时节点里面存放服务提供者中RMI的url,客户端可以访问对应的节点来访问具体的服务提供者),让服务消费者(Service Consumer)通过服务注册表获取具体的服务访问地址(也就是 RMI 服务地址)去访问具体的服务提供者。如下图所示:
详细实现原理图:
执行流程:
- 每一个服务提供者建立zk连接,并在zookeeper树形结构上注册一个顺序临时节点,临时节点里面存放服务提供者中RMI的url
- 服务消费者(Service Consumer)通过与服务注册表建立连接,获取节点中的数据来获取具体的服务访问地址,并添加监听节点
- 从列表中随机选取节点
- 通过RMI的url寻找对应的服务
- 获取远程服务对象xxServiceImpl
- 客户端调用RMI服务
- 返回结果
需要注意的是,服务注册表并不是 Load Balancer(负载均衡器),提供的不是“反向代理”服务,而是“服务注册”与“心跳检测”功能。利用服务注册表来注册 RMI 地址,这个很好理解,那么“心跳检测”又如何理解呢?说白了就是通过服务中心定时向各个服务提供者发送一个请求(实际上建立的是一个 Socket长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,只会从还“活 着”的服务提供者中选出一个做为当前的服务提供者 。也许服务中心可能会出现单点故障,如果服务注册表都坏掉了,整个系统也就瘫痪了。看来要想实现这个架构,必须保证服务中心也具备高可用性。ZooKeeper 正好能够满足我们上面提到的所有需求。
使用 ZooKeeper 的临时性 ZNode 来存放服务提供者的 RMI 地址,一旦与服务提供者的 Session 中断,会自动清除相应的 ZNode。让服务消费者去监听这些 ZNode,一旦发现 ZNode 的数据(RMI 地址)有变化,就会重新获取一份有效数据的拷贝。 ZooKeeper 与生俱来的集群能力(例如:数据同步与领导选举特性),可以确保服务注册表的高可用性。
3. 使用ZooKeeper实现RMI高可用代码剖析
3.1 通用接口
在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失效时间50sint ZK_SESSION_TIMEOUT = 5000;//服务注册表中使用的父节点String ZK_REGISTRY_PATH = "/registry";//子节点String ZK_PROVIDER_PATH = ZK_REGISTRY_PATH + "/provide";
}
3.2 服务端
在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() {@Overridepublic void process(WatchedEvent event) {//如果连接建立if (event.getState() == Event.KeeperState.SyncConnected) {//唤醒当前正在执行的线程latch.countDown();}}});//使当前线程处于等待状态latch.await();} catch (IOException | InterruptedException e) {LOGGER.error("", e);}return zk;}//创建ZNodeprivate void createNode(ZooKeeper zk, String url) {byte[] data = url.getBytes();//创建一个临时性且有序的ZNodetry {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";//端口:11214int 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]
3.3 客户端
在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() {@Overridepublic 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() {@Overridepublic 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;}
}
3.3 客户端下调用服务
在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 <rmi_host> <rmi_port>");
// 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";//端口:11214int 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);}
}
3.4 使用方法
根据以下步骤验证 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.NullPointerExceptionat com.bjsxt.remote.client.Client.main(Client.java:14)
3.5 总结
通过使用ZooKeeper实现了一个简单的RMI服务高可用性解决方案,通过ZooKeeper 注册所有服务提供者发布的 RMI 服务,让服务消费者监听 ZooKeeper的Znode,从而获取当前可用的 RMI 服务。
05 ZooKeeper分布式RMI协调实战相关推荐
- 架构系列---利用zookeeper 分布式锁解决缓存重建冲突实战
上一篇 分布式缓存重建并发冲突问题以及zookeeper分布式锁解决方案, 主要讲解了分布式缓存重建冲突原因及利用zookeeper分布式锁解决缓存重建冲突问题,本篇接着上篇,实现上篇思路,带你利用z ...
- 分布式技术与实战第一课 分布式理论与一致性算法
开篇词:搭建分布式知识体系,挑战高薪 Offer 你好,我是邴越,在一线互联网公司从事分布式开发工作多年,一直关注分布式理论和新技术的发展. 互联网发展到今天,用户数量越来越多,产生的数据规模也越来越 ...
- 什么是ZooKeeper?可以做什么?ZooKeeper分布式事务详解篇
前言 什么是ZooKeeper,你真的了解它吗.我们一起来看看吧~ 一.什么是 ZooKeeper? ZooKeeper 是 Apache 的一个顶级项目,为分布式应用提供高效.高可用的分布式协调服务 ...
- [转载] zookeeper 分布式锁服务
转载自http://www.cnblogs.com/shanyou/archive/2012/09/22/2697818.html 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那 ...
- Zookeeper分布式一致性原理(四):Zookeeper简介
zookeeper是一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现数据发布/订阅.负载均衡.命名服务.分布式协调/通知.集群管理.master选举.分布式锁和分布式队列等.Zook ...
- [原创].NET 分布式架构开发实战之一 故事起源
阅读: 1320 评论: 18 作者: 小洋(燕洋天工作室) 发表于 2010-05-23 09:03 原文链接 .NET 分布式架构开发实战之一 故事起源 前言:本系列文章主要讲述一个实实在在的项目 ...
- 分布式锁原理——redis分布式锁,zookeeper分布式锁
首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...
- 关于分布式锁原理的一些学习与思考:redis分布式锁,zookeeper分布式锁
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:牛人 20000 字的 Spring Cloud 总结,太硬核了~ 作者:队长给我球. 出处:https://w ...
- zookeeper 分布式锁服务
分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那一层来挡.当大量的行锁.表锁.事务充斥着数据库的时候.一般web应用很多的瓶颈都在数据库上,这里给大家介绍的是减轻数据库锁负担的一种 ...
- zookeeper 分布式锁_关于redis分布式锁,zookeeper分布式锁原理的一些学习与思考
编辑:业余草来源:https://www.xttblog.com/?p=4946 首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法 ...
最新文章
- MongoDB时间类型
- 鸿蒙台湾乌龙茶,郭碧婷生女再闹乌龙?向太守口如瓶,向佐有意回避,其实另有隐情...
- 域控制器显示无法使用解决办法
- Tomcat源码解析五:Tomcat请求处理过程
- hive 导出json格式 文件_hive存储json格式文件
- App界面交互设计规范(转)
- Oracle数据库常用undo查询思路
- 轨迹跟踪主要方法_带你入门多目标跟踪(一)领域概述
- 【Java从0到架构师】Spring - AOP
- VUE3.0引入本地js文件
- maven下载,安装与eclipse中maven配置
- python实现键盘记录木马_Python告诉你木马程序的键盘记录原理
- java csv下载_javacsv.jar
- 液压传动与控制QY-QDSY16
- html语言亚马逊后台,「亚马逊」新手卖家指南-页面与术语
- Goland 1.15运行报错:该版本的 %1 与你运行的 Windows 版本不兼容
- 如何下载B站视频以及音频
- gif制作转换器免费推荐,动图制作什么软件好用
- 开源自主导航小车MickX4(三)底盘ROS节点
- HDU 2191 汶川大地震