一、RPC基本结构
\quad\quad
RPC(Remote Procedure Call),即远程过程调用,主要应用在分布式应用中,将服务部署在不同的机器上,通过RPC框架调用远程服务器中的内容。RPC 框架包含三个最重要的组件,分别是客户端、服务端和注册中心。在一次 RPC 调用流程中,这三个组件是这样交互的:
- 服务端在启动后,会将它提供的服务列表发布到注册中心,客户端向注册中心订阅服务地址;
- 客户端会通过本地代理模块 Proxy 调用服务端,Proxy 模块收到负责将方法、参数等数据转化成网络字节流;
- 客户端从服务列表中选取其中一个的服务地址,并将数据通过网络发送给服务端;
- 服务端接收到数据后进行解码,得到请求信息;
- 服务端根据解码后的请求信息调用对应的服务,然后将调用结果返回给客户端。
二、实现简单的RPC
\quad\quad
但本文主要参考手写RPC框架(一)、如何实现一个简单的RPC来实现简单的RPC框架,简单RPC框架采用客户端(Consumer)/服务端(Provider)的模式,这里并没有实现注册中心。同时这篇文章仅仅实现了对Calculator这个类的远程调用,缺乏通用性,之后会逐步实现服务注册,动态代理,负载均衡等功能,逐步完善该RPC框架。
\quad\quad
本文实现的是一个计算器类的RPC框架,客户端通过调取程序,服务端来返回计算结果,其主要流程如下。
- 客户端调用函数,如add()
- 将调用信息(调用的类、方法、方法传参等)序列化,并通过socket等方式将序列化信息得到的数据包发送给Provider
- Provider从数据包中反序列化得到调用信息,Provider执行请求,并将返回结果序列化
- 将序列化后的数据包发给Consumer,Consumer反序列化接收到的数据
2.1 服务端
2.1.1 接口、实现类和序列化
- 计算器类接口
public interface Calculator {
int add(int a, int b);
//int sub(int a, int b);
//int mul(int a, int b);
//int div(int a, int b);
}
- 计算器类接口实现
public class CalculatorImpl implements Calculator{
@Override
public int add(int a, int b) {
return a+b;
}
/*
@Override
public int sub(int a, int b) {
return a-b;
}
@Override
public int mul(int a, int b) {
return a*b;
}
@Override
public int div(int a, int b) {
if(b==0){
throw new ArithmeticException();
}
else{
return a/b;
}
}
*/
}
- 定义远程调用时,客户端发送给服务端的消息体,因为该类需要序列化后传递,所以需要实现Serializable
package org.example;
import java.io.Serializable;
public class CalculateRpcRequest implements Serializable {
private static final long serialVersionUID = 7503710091945320739L;
private int a;
private int b;
private String name;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "CalculateRpcRequest{" +
"a=" + a +
", b=" + b +
", name='" + name + '\'' +
'}';
}
}
2.1.2 服务端逻辑
\quad\quad 接收客户端序列化后的请求信息,执行对应方法。如本文中此处接收add方法及a,b变量,故在服务端调用add()方法实现类进行计算,并将计算结果序列化后传递给客户端。
public class Server {
private final Calculator calculator = new CalculatorImpl();
public static void main(String[] args) throws IOException {
new Server().run();
}
public void run() throws IOException {
//8889端口上监听数据
ServerSocket listener = new ServerSocket(8889);
try {
Socket socket = listener.accept();
while (true) {
try {
//反序列化数据
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object object = objectInputStream.readObject();
int res = 0;
if (object instanceof CalculateRpcRequest) {
CalculateRpcRequest calculateRpcRequest = (CalculateRpcRequest) object;
//判断执行方法
if ("add".equals(calculateRpcRequest.getName())) {
//执行该方法
res = calculator.add(calculateRpcRequest.getA(), calculateRpcRequest.getB());
} else {
throw new UnsupportedOperationException();
}
}
//返回数据序列化
ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(new Integer (res));
} catch (Exception e) {
System.out.println("反序列失败!");
} finally {
socket.close();
}
}
} finally {
listener.close();
}
}
}
2.2 客户端
2.2.1 客户端逻辑
\quad\quad 客户端发送请求信息,将调用方法和参数序列化后传递给服务端,并接收方法执行后返回的结果。
public class CalculatorRemoteImpl implements Calculator {
//重写Calculator中的方法
@Override
public int add(int a, int b) {
//远程调用的地址
String address="127.0.0.1";
try{
//和远程服务建立socket连接
Socket socket=new Socket(address,PORT);
//消息体序列化
CalculateRpcRequest calculateRpcRequest=generateRequest(a,b);
//初始化对象的序列化流,把对象转成字节数据,并通过socket发送
ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
//写入调用的消息体
objectOutputStream.writeObject(calculateRpcRequest);
//获取返回数据
ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
Object response =objectInputStream.readObject();
System.out.println(response);
if(response instanceof Integer){
return (Integer) response;
}
else{
throw new InternalError();
}
} catch (Exception e) {
System.out.println("ERROR!");
throw new InternalError();
}
}
//消息体实例化
public CalculateRpcRequest generateRequest(int a,int b){
CalculateRpcRequest calculateRpcRequest=new CalculateRpcRequest();
calculateRpcRequest.setA(a);
calculateRpcRequest.setB(b);
calculateRpcRequest.setName("add");
return calculateRpcRequest;
}
public static final int PORT = 8889;
}
2.3 通过RPC远程调用
public class Client {
public static void main(String[] args) {
//实例化,发起rpc请求
CalculatorRemoteImpl cal=new CalculatorRemoteImpl();
//远程调用
int res=cal.add(1000,210);
System.out.println("res= "+res);
}
}