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)进行分离,充分体现组件之间的弱耦合,系统架构更易于扩展

05 ZooKeeper分布式RMI协调实战_第1张图片

我们先从通过一个最简单的 RMI 服务与调用示例,快速掌握 RMI 的使用方法,然后指RMI 的局限性,最后笔者对此问题提供了一种简单的解决方案,即使用 ZooKeeper 轻松解决 RMI 调用过程中所涉及的问题。

1. Java原生RMI实现

  RMI细节流程如下:

05 ZooKeeper分布式RMI协调实战_第2张图片

再实现RMI服务前,我们搭建框架如下:

05 ZooKeeper分布式RMI协调实战_第3张图片

1.1 发布RMI服务

  发布RMI服务,我们只需要做三件事:

  1. 定义一个RMI接口
  2. 编写RMI接口的实现类
  3. 通过JNDI发布RMI服务
1.1.1 定义一个RMI接口

  在common包下,新建HelloService类,接口选择:Interface接口。

05 ZooKeeper分布式RMI协调实战_第4张图片

  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接口。

05 ZooKeeper分布式RMI协调实战_第5张图片

  实现以上的 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;
    }
}
1.1.3 通过JNDI发布RMI服务

   在server包下,新建RMIServer类,接口选择:Class接口。

05 ZooKeeper分布式RMI协调实战_第6张图片

发布 RMI 服务,我们需要告诉 JNDI 三个基本信息:

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

  2. 端口号(port)、

  3. 服务名(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 服务。

1.2 调用RMI服务

   在client包下,新建RMIClient类,接口选择:Class接口。

05 ZooKeeper分布式RMI协调实战_第7张图片

  同样我们也使用一个 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 {
        //定义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 调用成功。

05 ZooKeeper分布式RMI协调实战_第8张图片

我们在客户端远程调用了服务器端的服务,这就是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 所携带的数据是通过“应用层”来传输的,传输层较应用层更为底层,越底层越快。

05 ZooKeeper分布式RMI协调实战_第9张图片

既然 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模型七层网络结构

    05 ZooKeeper分布式RMI协调实战_第10张图片

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

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

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

05 ZooKeeper分布式RMI协调实战_第11张图片

详细实现原理图

05 ZooKeeper分布式RMI协调实战_第12张图片

执行流程:

  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接口:

05 ZooKeeper分布式RMI协调实战_第13张图片

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";
}
3.2 服务端

   在server包下,定义ServiceProvider类:
05 ZooKeeper分布式RMI协调实战_第14张图片
  需要编写一个 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]
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() {
                @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;
    }
}
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  ");
//            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);

    }
}
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.NullPointerException
	at com.bjsxt.remote.client.Client.main(Client.java:14)
3.5 总结

  通过使用ZooKeeper实现了一个简单的RMI服务高可用性解决方案,通过ZooKeeper 注册所有服务提供者发布的 RMI 服务,让服务消费者监听 ZooKeeper的Znode,从而获取当前可用的 RMI 服务。

你可能感兴趣的:(#,03,分布式,zookeeper,java,RMI)