初步了解RPC及实现一个简单的RPC
2019-02-13 本文已影响62人
一萍之春
什么是RPC
RPC(全称为 Remote Procedure Call)即远程过程调用
在维基百科中这样解释
远程过程调用(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。
RPC解决的事情
- 进程间通讯
- 提供和本地方法调用一样的调用机制
- 屏蔽程序员对远程调用的细节实现
简单的RPC结构
简单的RPC可以分为五个部分:
- client
- client-stub
- network-service
- server-stub
- server
这5个部分的关系及基本流程如下图
关系及基本流程图
RPC基本过程
- 服务消费方(client)以本地调用方式调用服务;
- client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
- client stub找到服务地址,并将消息发送到服务端;
- server stub收到消息后进行解码;
- server stub根据解码结果通过反射调用本地的服务;
- 本地服务执行并将结果返回给server stub;
- server stub将返回结果打包成消息并发送至消费方;
- client stub接收到消息,并进行解码;
- 服务消费方得到最终结果。
一般情况下我们的RPC就是对我们的上面过程的2~8进行封装然后,给client的程序员感觉就像调用本地的方法一样。
简单实现
首先是Client端的应用层怎么发起RPC,CallerApp
public class CallerApp {
public static void main(String[] args) {
Calculator calculator = new CalculatorRemoteImpl();
int result = calculator.add(3, 3);
System.out.println(result);
}
}
通过一个CalculatorRemoteImpl,我们把RPC的逻辑封装进去了,客户端调用时感知不到远程调用的麻烦。将上面的2、3、4进行封装
public class CalculatorRemoteImpl implements Calculator {
public static final int PORT = 9090;
private static Logger log = LoggerFactory.getLogger(CalculatorRemoteImpl.class);
public int add(int a, int b) {
List<String> addressList = lookupProviders("Calculator.add");
String address = chooseTarget(addressList);
try {
Socket socket = new Socket(address, PORT);
// 将请求序列化
CalculateRpcRequest calculateRpcRequest = generateRequest(a, b);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
// 将请求发给服务提供方
objectOutputStream.writeObject(calculateRpcRequest);
// 将响应体反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object response = objectInputStream.readObject();
log.info("response is {}", response);
if (response instanceof Integer) {
return (Integer) response;
} else {
throw new InternalError();
}
} catch (Exception e) {
log.error("fail", e);
throw new InternalError();
}
}
private CalculateRpcRequest generateRequest(int a, int b) {
CalculateRpcRequest calculateRpcRequest = new CalculateRpcRequest();
calculateRpcRequest.setA(a);
calculateRpcRequest.setB(b);
calculateRpcRequest.setMethod("add");
return calculateRpcRequest;
}
private String chooseTarget(List<String> providers) {
if (null == providers || providers.size() == 0) {
throw new IllegalArgumentException();
}
return providers.get(0);
}
public static List<String> lookupProviders(String name) {
List<String> strings = new ArrayList();
strings.add("127.0.0.1");
return strings;
}
}
这里用到了Socket来进行远程通讯,同时利用ObjectOutputStream的writeObject和ObjectInputStream的readObject,来实现序列化和反序列化。
下面是service端的实现
public class CalleeApp {
private static Logger log = LoggerFactory.getLogger(CalleeApp.class);
private Calculator calculator = new CalculatorImpl();
public static void main(String[] args) throws IOException {
new CalleeApp().run();
}
private void run() throws IOException {
ServerSocket listener = new ServerSocket(9090);
try {
while (true) {
Socket socket = listener.accept();
try {
// 将请求反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object object = objectInputStream.readObject();
log.info("request is {}", object);
// 调用服务
int result = 0;
if (object instanceof CalculateRpcRequest) {
CalculateRpcRequest calculateRpcRequest = (CalculateRpcRequest) object;
if ("add".equals(calculateRpcRequest.getMethod())) {
result = calculator.add(calculateRpcRequest.getA(), calculateRpcRequest.getB());
} else {
throw new UnsupportedOperationException();
}
}
// 返回结果
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(new Integer(result));
} catch (Exception e) {
log.error("fail", e);
} finally {
socket.close();
}
}
} finally {
listener.close();
}
}
}
示例代码在GitHub上下载