文章目录

  • 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服务,我们只需要做三件事:

  1. 定义一个RMI接口
  2. 编写RMI接口的实现类
  3. 通过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 三个基本信息:

  1. 域名或 IP 地址(host)、

  2. 端口号(port)、

  3. 服务名(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 服务,相比发布而言,调用会更加简单,我们只需要知道两个东西:

  1. RMI 请求路径、
  2. 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服务主要有以下两点局限性

  1. RMI 使用了 Java 默认的序列化方式,对于性能要求比较高的系统,可能需要使用其它序列化方案来解决(例如:Protobuf)。
  2. RMI 服务在运行时难免会存在出故障,例如,如果 RMI 服务无法连接了,就会导致客户端无法响应的现象,存在类似于“单点故障”的问题。

  在一般的情况下,Java 默认的序列化方式确实已经足以满足我们的要求了,如果性能方面不是问题的话,我们需要解决的实际上是第二点,也就是说,让系统具备 HA(High Availability,高可用性)。

备注:

  1. https://www.cnblogs.com/wlzjdm/p/7856356.html(JNDI理解)

    JNDI是一个命名目录接口,提供与外界的一个访问关系,只要相关应用、设备能提供服务,那么我们就可以通过JNDI来连接处理。

  2. OSI模型七层网络结构

2. 使用ZooKeeper提供高可用的RMI服务原理分析

  使用Java原生的RMI的例子中,我们的服务端存在单点故障。如果服务器端down掉,我们在使用客户端请求时会无响应。接下来,我们使用ZooKeeper实现高可用的RMI服务。

  要想解决 RMI 服务的高可用性问题,我们需要利用 ZooKeeper 充当一个服务注册表(Service Registry),让多个服务提供者(Service Provider)形成一个集群(每一个服务提供者在zookeeper树形结构上注册一个临时节点,临时节点里面存放服务提供者中RMI的url,客户端可以访问对应的节点来访问具体的服务提供者),让服务消费者(Service Consumer)通过服务注册表获取具体的服务访问地址(也就是 RMI 服务地址)去访问具体的服务提供者。如下图所示:

详细实现原理图

执行流程:

  1. 每一个服务提供者建立zk连接,并在zookeeper树形结构上注册一个顺序临时节点,临时节点里面存放服务提供者中RMI的url
  2. 服务消费者(Service Consumer)通过与服务注册表建立连接,获取节点中的数据来获取具体的服务访问地址,并添加监听节点
  3. 从列表中随机选取节点
  4. 通过RMI的url寻找对应的服务
  5. 获取远程服务对象xxServiceImpl
  6. 客户端调用RMI服务
  7. 返回结果

需要注意的是,服务注册表并不是 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 服务的高可用性:

  1. 运行两个 Server 程序,一定要确保 port 是不同的。

  2. 运行一个 Client 程序。

  3. 停止其中一个 Server 程序,并观察 Client 控制台的变化(停止一个 Server 不会导致 Client 端调用失败)。

  4. 重新启动刚才关闭的 Server 程序,继续观察 Client 控制台变化(新启动的 Server会加入候选)。

  5. 先后停止所有的 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协调实战相关推荐

  1. 架构系列---利用zookeeper 分布式锁解决缓存重建冲突实战

    上一篇 分布式缓存重建并发冲突问题以及zookeeper分布式锁解决方案, 主要讲解了分布式缓存重建冲突原因及利用zookeeper分布式锁解决缓存重建冲突问题,本篇接着上篇,实现上篇思路,带你利用z ...

  2. 分布式技术与实战第一课 分布式理论与一致性算法

    开篇词:搭建分布式知识体系,挑战高薪 Offer 你好,我是邴越,在一线互联网公司从事分布式开发工作多年,一直关注分布式理论和新技术的发展. 互联网发展到今天,用户数量越来越多,产生的数据规模也越来越 ...

  3. 什么是ZooKeeper?可以做什么?ZooKeeper分布式事务详解篇

    前言 什么是ZooKeeper,你真的了解它吗.我们一起来看看吧~ 一.什么是 ZooKeeper? ZooKeeper 是 Apache 的一个顶级项目,为分布式应用提供高效.高可用的分布式协调服务 ...

  4. [转载] zookeeper 分布式锁服务

    转载自http://www.cnblogs.com/shanyou/archive/2012/09/22/2697818.html 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那 ...

  5. Zookeeper分布式一致性原理(四):Zookeeper简介

    zookeeper是一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现数据发布/订阅.负载均衡.命名服务.分布式协调/通知.集群管理.master选举.分布式锁和分布式队列等.Zook ...

  6. [原创].NET 分布式架构开发实战之一 故事起源

    阅读: 1320 评论: 18 作者: 小洋(燕洋天工作室) 发表于 2010-05-23 09:03 原文链接 .NET 分布式架构开发实战之一 故事起源 前言:本系列文章主要讲述一个实实在在的项目 ...

  7. 分布式锁原理——redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  8. 关于分布式锁原理的一些学习与思考:redis分布式锁,zookeeper分布式锁

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:牛人 20000 字的 Spring Cloud 总结,太硬核了~ 作者:队长给我球. 出处:https://w ...

  9. zookeeper 分布式锁服务

    分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那一层来挡.当大量的行锁.表锁.事务充斥着数据库的时候.一般web应用很多的瓶颈都在数据库上,这里给大家介绍的是减轻数据库锁负担的一种 ...

  10. zookeeper 分布式锁_关于redis分布式锁,zookeeper分布式锁原理的一些学习与思考

    编辑:业余草来源:https://www.xttblog.com/?p=4946 首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法 ...

最新文章

  1. MongoDB时间类型
  2. 鸿蒙台湾乌龙茶,郭碧婷生女再闹乌龙?向太守口如瓶,向佐有意回避,其实另有隐情...
  3. 域控制器显示无法使用解决办法
  4. Tomcat源码解析五:Tomcat请求处理过程
  5. hive 导出json格式 文件_hive存储json格式文件
  6. App界面交互设计规范(转)
  7. Oracle数据库常用undo查询思路
  8. 轨迹跟踪主要方法_带你入门多目标跟踪(一)领域概述
  9. 【Java从0到架构师】Spring - AOP
  10. VUE3.0引入本地js文件
  11. maven下载,安装与eclipse中maven配置
  12. python实现键盘记录木马_Python告诉你木马程序的键盘记录原理
  13. java csv下载_javacsv.jar
  14. 液压传动与控制QY-QDSY16
  15. html语言亚马逊后台,「亚马逊」新手卖家指南-页面与术语
  16. Goland 1.15运行报错:该版本的 %1 与你运行的 Windows 版本不兼容
  17. 如何下载B站视频以及音频
  18. gif制作转换器免费推荐,动图制作什么软件好用
  19. 开源自主导航小车MickX4(三)底盘ROS节点
  20. HDU 2191 汶川大地震

热门文章

  1. powershell excel 导入 sqlserver
  2. Visual Studio的.NET内存分配分析器解析
  3. individual program总结2.0
  4. 【转载】SQL Server 2005关于数据类型最大值(3)
  5. 请求参数完整性校验,解决流只能写一次的问题
  6. 最常用的css垂直居中方法
  7. UVALive - 5713 最小生成树
  8. java中间==、equals和hashCode差额
  9. 英文拼写及语法检查软件
  10. memcached在Java中的应用以及magent的配置-每天进步一点点