RPC和RMI

RPC和RMI

RPC 全称为 Remote Proceduer Call,即远程过程调用。借助 RPC 可以做到像本地调用一样去调用远程服务,是进程间的通信方式。比如:两台服务器 A 和 B,A 服务器上部署一个应用,B 服务器上部署一个应用,A 服务器上的应用想调用 B 服务器上的应用提供的方法,但是两个应用不在同一个内存空间,互相就不能直接调用。这个时候需要通过网络来表达调用的语义和传达调用的数据。需要注意的是 RPC 并不是一个具体的技术,而是指整个网络远程调用过程。


在分布式框架中,一个最基础的问题就是远程服务是怎么通讯的,在 Java 领域中又很多可以实现远程通讯的技术;例如:RMI、Hessian、SOAP、ESB 和 JMS 等。它们是基于什么原理实现的呢?

RPC 实现的基本思想

首先我们实现一个 RPC 就是为了实现机器在网络间的通信。那么我们就得先清楚明白计算机网络通信的 基本原理,在底层层面去看,网络通信需要做的是将流从一台计算机传输到另一台计算机。基于传输协议和网络 IO 来实现,其中常见的传输协议有 TCP、UDP 等。TCP 和 UDP 是基于 Socket 扩展出的一种传输协议。网络 IO 主要有 BIO、NIO、AIO 三种方式,所有的分布式应用通讯都基于这个原理来实现的。

RPC 组成架构

一个完整的 RPC 架构里包含了四个核心的组件,分别是 Client、Client Stub、Server 以及 Server Stub,这个 Stub 可以理解为存根。

  • Client(客户端):服务的调用者

  • Client Stub(客户端存根):存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方

  • Server(服务端):服务的提供者

  • Server Stub(服务端存根):接收客户端发送过来的消息,将消息解包,并调用本地的方法

RPC 调用过程

1、客户端(Client)以本地方式调用(即以接口的方式)调用服务;

2、客户端存根(Client Stub)接收到调用后,负责将方法,参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);

3、客户端通过 Socket 将消息发送到服务端;

4、服务端存根(Server Stub)收到消息后进行解码(将消息体对象反序列化);

5、服务端存根(Server Stub)根据解码结果调用本地的服务;

6、服务器进行业务处理;

7、本地服务执行并将结果返回给服务端存根(Server Stub);

8、服务端存根(Server Stub)将返回结果打包成消息(将结果消息对象序列化);

9、服务端(Server)通过 Socket 将消息发送到客户端;

10、客户端存根(Clinet Stub)接收到结果消息,并进行解码(将结果消息反序列化);

11、客户端(Client)接收结果。

上面是 RPC 调用的一个整体流程,而 RPC 想把 2、3、4、5、7、8、9、10 这些步骤都封装起来了。只剩下1、6、11(注意:无论是何种的数据类型,最终都需要转换成二进制流在网络上进行传输,数据的发送方需要将对象转换为二进制流,而数据的接收方则需要把二进制流再恢复为对象。)

Java 中 RPC 框架比较多,常见的有 Hessian、gRPC、Dubbo 等,其实对于 RPC 框架而言,核心模块就是通讯和序列化。

RMI

RMI 概念和调用流程

Java RMI,即远程方法调用(Remote Method Invocation),一种基于实现远程过程调用(RPC-Remote procedure call)的 Java API,能直接传输序列化后的 Java 对象。它们的实现依赖于 Java 虚拟机,因此它仅支持从一个 JVM 到另一个 JVM 的调用。

1、客户端从远程服务器的注册表中查询并获取远程对象引用;

2、桩对象与远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际上是由相应的桩对象代理完成的;

3、远程引用层在将桩的本地引用转换为服务器上对象的远程引用后,再将调用传递给传输层(Transport),由传输层通过 TCP 协议发送调用;

4、在服务器端,监听入站连接,它一旦接收到客户端远程调用后,就将这个引用转发给其上层的远程引用层;远程引用层将客户端发送的远程应用转换为本地虚拟机的引用后,再将请求传递给骨架(Skeleton);骨架读取参数,又将请求传递给服务器,最后由服务器进行实际的方法调用。

5、如果远程方法调用后有返回值,服务器会将结果沿着“骨架-> 远程引用层 -> 传输层”向下传递;

6、客户端的传输层接收到返回结果后,会沿着“传输层 -> 远程引用层 -> 桩”向上传递,然后由桩来反序列化这些返回值,并将最终的结果传递给客户端程序。

RMI 的具体实现
需求分析:
  • 服务端提供查询数据的方法(例如:根据 ID 查询用户信息)

  • 客户端调用服务端的方法,并返回结果对象(这里是用户对象)

  • 要求使用 RMI 进行远程通信

示例代码

1、创建远程对象(示例:User 对象)

package rmi.pojo;

import java.io.Serializable;

public class User implements Serializable {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2、远程调用的服务

将远程对象注册到服务器上需要满足两个条件:

  • IUserService 需要继承 Remote 类
  • IUserServiceImpl 需要继承 UnicastRemoteObject 类

并且当我们使用 RMI 进行编程时,其接口中的所有方法都需要 抛出 RemoteException 异常;

package rmi.service;

import rmi.pojo.User;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IUserService extends Remote {
    /**
     * 根据ID 查询用户的方法
     */
    public User findById(int id) throws RemoteException;
}

package rmi.service;

import rmi.pojo.User;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;

public class IUserServiceImpl extends UnicastRemoteObject implements IUserService {
    Map<Object,User> userMap = new HashMap();
    public IUserServiceImpl() throws RemoteException {
        super();
        User user1 = new User();
        user1.setId(1);
        user1.setName("张三");
        User user2 = new User();
        user1.setId(2);
        user1.setName("李四");
        userMap.put(user1.getId(),user1);
        userMap.put(user2.getId(),user2);
    }

    @Override
    public User findById(int id) throws RemoteException {
        return userMap.get(id);
    }
}

3、RMI Server

  • 创建 RMI Server 时 我们需要先注册 Registry 实例,绑定服务器的端口号

  • 然后创建远程对象

  • 将远程对象注册到 RMI 服务器上

package rmi;

import rmi.service.IUserService;
import rmi.service.IUserServiceImpl;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
    public static void main(String[] args) {
        try {
            // 1 注册 Registry 实例,绑定端口
            Registry registry = LocateRegistry.createRegistry(9999);
            // 2 创建远程对象
            IUserService userService = new IUserServiceImpl();
            // 3 将远程对象注册到 RMI 服务器上
            registry.rebind("userService ",userService);
            System.out.println("RMI 服务端启动成功!");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

}

4、RMI Client

  • RMI 客户端需要先获取到 Registry 实例对象(需要知道 RMI 服务器的 IP 和端口号)
  • 通过 Registry 实例查找到对应的远程对象(registry.lookup(“注册时的服务名”))
package rmi;

import rmi.pojo.User;
import rmi.service.IUserService;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient {
    public static void main(String[] args) throws RemoteException {
        // 1 获取 registry 实例
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
        // 2 通过 registry 实例查找对应的远程对象
        try {
            IUserService userService = (IUserService) registry.lookup("userService");
            User user = userService.findById(1);
            System.out.println(user.getId()+"-----"+ user.getName());
        } catch (NotBoundException e) {
            e.printStackTrace();
        }
    }
}

5、运行 RMI Server (启动 RMI 服务),然后启动 RMI Client,我们可以通过 userService.findById() 方法查询到用户的信息。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值