RPC是什么

Remote Procedure Call,远过程调用

所谓远过程调用通俗地讲,就是某个程序调用了远方另一个非本程序的方法,本质上就是进程之间的网络通信。更进一步说,每一个进程都有一个自己的地址空间,如果该进程调用的方法存在于本进程的地址空间,那么就是普通的本地调用;若该进程调用的方法不存在本进程的地址空间中,而是在另一个进程的地址空间中,那么就是远过程调用。

RPC - 图1

RPC 与 HTTP

不同程序之间的通信方式有很多,比如浏览器和服务器之间的 HTTP 协议。与 RPC 相比,HTTP 有统一的标准,因此更加通用、兼容性好,支持不同语言。而且基于文本的 HTTP 有很好的可读性。但是 HTTP 缺点也很明显:

  • 基于 HTTP 的报文存在过多的冗余信息,而 RPC 常采用自定义的协议格式,内容更加直接,减少报文冗余;
  • RPC 更加可定制化,可以采用更加高效的二进制序列化协议,将文本转换成二进制传输,获得更高的性能

实现一个简单 RPC 框架

分析一下实现一个简单 rpc 框架的过程。

C/S模型

方法的请求者就是client方,方法的提供者就是我们的server方,两者通过网络通信来传递数据。

RPC - 图2

  • client端。client方要使用服务,最终就是要使用方法,所以client至少需要提供:
    • 调用方法的类,即谁来调用方法;
    • 方法名,方法参数,即确定调用哪一个方法;
  • server端。server端主要就是提供方法调用的服务,所以需要有以下的职责:
    • 注册可用方法,要提供方法首先就要有方法;
    • 获取服务,返回服务结果,即给client提供远程调用的服务;
public interface ServiceCenter {
    // 开始监听网络端口,提供服务
    void start() throws IOException;

    // 停止服务
    void stop();

    // 注册服务接口及对应实现
    void register(Class<?> serviceInterface, Class<?> impl); 

    // 用于客户获取服务端口
    int getPort();
}

网络通信

远程的通信需要通过网络来实现,网络的传输本质上就是字节序列的搬运,所以无论是客户端发出的请求还是服务端返回的服务,都需要序列化成字节序列发送。对象变成字节序列使用的是序列化方法,反之从字节序列还原一个对象使用的就是反射的方式了。

RPC - 图3

为了能够很好的解释来自客户端的 rpc 请求到底是什么含义,双方都需要对请求的结构达成一个协议:

public class RpcRequest implements Serializable {
    private String serviceName; // 服务对象的名称
    private String methodName;  // 要使用服务对象的哪个方法
    private Class<?>[] paraTypes;   // 方法参数类型
    private Object[] params;    // 方法参数列表

    // constructor, getters and setters ...
}

server 服务提供

服务中心启动之后的主要任务就是:监听网络请求,获取到请求后,根据请求的参数从注册中心中找到方法并调用,接着通过网络返回结果。所以服务中心服务提供的主要逻辑如下:

public void start() throws IOException {
    // 1. 服务中心拥有了socket才打开了网络的通信的门
    try (ServerSocket serverSocket = new ServerSocket()) {
        serverSocket.bind(new InetSocketAddress(port)); // socket = (ip地址,端口)
        System.out.println("Service center start :)");
        while (true) {
            // 2. 阻塞等待客户连接
            // 3. 接收到tcp请求就交给一个线程进行处理
            threadPool.execute(new ServiceTask(serverSocket.accept()));
        }
    }
}

可以发现在服务端 socket 接收到一个连接后,再进行了一次包装成 ServiceTask。ServiceTask 是一个自定义的类,封装了 rpc 请求的处理,里边的主要逻辑是:

  • 通过 socket 获取 tcp 的字节流输入,进而还原出一个 rpc 请求对象;
  • 根据 rpc 请求的各个参数从注册中心获取服务对象;
  • 服务对象调用方法得到结果,再通过 socket 写回给客户端。

client 服务获取

客户端最终是通过反射字节码的方式得到服务的,所以可以使用 JDK 提供的动态代理方法来动态获取一个服务对象的代理,接着通过该代理对象来间接调用远程服务器的方法。所以与服务器的网络通信实际上在代理对象内部进行完成,客户只是把这个代理对象当作其服务的提供者。

RPC - 图4