1.基本概念
RPC是Remote Procedure Call的缩写,即远程过程调用。最基本的RPC模型如下图所示。
在下图中,服务提供者A Server、服务消费者B Server。服务消费者只需要通过接口,就可以远程调用服务提供者提供的对应的接口的实现,从而获取返回值,完成对应的调用过程。
2.具体实现
以下上代码实例,通过Socket的方式,来实现自己的RPC框架。项目结构如下图:
项目分为四个模块:rpc模块、公共接口模块、服务提供者模块、服务消费者模块。
2.1 rpc模块
rpc模块主要实现了RPC远程过程调用的功能。该模块中,分为三个部分,client端、server端、通信协议。如下图所示。
2.1.1通信协议
在RPC框架中,客户端的方法要通过接口调用服务端对应接口的实现类的方法,需要提供必要的信息。具体包括:接口名称、方法名称、参数类型列表、参数值列表。代码如下所示:
package com.supger.rpc.core.protocol;
import java.io.Serializable;
/**
* RPC框架请求协议类,由于这个类需要在网络中传输,需要实现序列化接口。
*/
public class RequestProtocol implements Serializable {
/**
* 接口全名称
*/
private String interfaceClassName;
/**
* 方法名称
*/
private String methodName;
/**
* 参数类型列表
*/
private Class<?>[] parameterTypes;
/**
* 参数值列表
*/
private Object[] parameterValues;
public String getInterfaceClassName() {
return interfaceClassName;
}
public void setInterfaceClassName(String interfaceClassName) {
this.interfaceClassName = interfaceClassName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class<?>[] getParameterTypes() {
return parameterTypes;
}
public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
public Object[] getParameterValues() {
return parameterValues;
}
public void setParameterValues(Object[] parameterValues) {
this.parameterValues = parameterValues;
}
}
2.1.2客户端
在RPC框架中,客户端通过动态代理,在动态代理中,把需要调用的服务端的接口的相关信息序列化,最终通过Socket的方式,发送给服务端,等待服务端完成调用,最终把结果写入Socket,动态代理内部,获取返回结果。返回给调用方。代码如下所示:
package com.supger.rpc.core.client;
import com.supger.rpc.core.protocol.RequestProtocol;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* RPC框架客户端核心实现类
*/
public class RpcClient {
/**
* 通过动态代理获取调用接口对应实例
* @param interfaceClass
* @param address
* @param <T>
* @return
*/
public static <T> T getRemoteProxy(Class<T> interfaceClass, InetSocketAddress address){
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class<?>[]{
interfaceClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try(Socket socket = new Socket()){
// 通过网络连接服务端
socket.connect(address);
try(
// 获取输出流
ObjectOutputStream serializer = new ObjectOutputStream(socket.getOutputStream());
// 获取输入流
ObjectInputStream deSerializer = new ObjectInputStream(socket.getInputStream())
){
// 创建一个RPC框架中请求协议对象
RequestProtocol requestProtocol = new RequestProtocol();
// 填充属性
requestProtocol.setInterfaceClassName(interfaceClass.getName());
requestProtocol.setMethodName(method.getName());
requestProtocol.setParameterTypes(method.getParameterTypes());
requestProtocol.setParameterValues(args);
// 序列化协议对象(把数据放入到网络中)
serializer.writeObject(requestProtocol);
/**
* -----------------------
* 同步过程
* -----------------------
*/
// 反序列化(从网络中获取服务端放入的数据)
Object result = deSerializer.readObject();
return result;
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
});
}
}
2.1.3服务端
在RPC框架中,服务端需要启动,监听对应的端口。客户端来连接对应的端口。启动监听后,当有多个客户端请求来的时候,为了不因为前一个请求未处理完成而发生阻塞,需要引入多线程机制。当服务端监听到客户端连接的时候,用线程池来处理对应的任务。任务中保存着对应获取的Socket连接信息。在任务的内部,run方法中,实现对应的获取客户端的调用信息,通过反射的方式,调用对应的实现类。而在此之前,服务端需要暴露对应的服务列表。代码如下所示:
package com.supger.rpc.core.server;
import com.supger.rpc.core.protocol.RequestProtocol;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* RPC框架服务端的核心实现类
* 核心实现步骤:
* 1、暴露需要调用的服务接口
* 2、启动服务端
*/
public class RpcServer {
/**
* 定义存储暴露的服务列表
*/
Map<String,Object> serverMap = new ConcurrentHashMap<>(32);
/**
* 定义一个线程池
*/
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(8,20,200, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(10));
/**
* 暴露服务的方法
* @param interfaceClass
* @param instance
*/
public void publishServiceAPI(Class<?> interfaceClass,Object instance){
this.serverMap.put(interfaceClass.getName(),instance);
}
/**
* 定义发布服务的方法
* @param port
*/
public void start(int port){
// 创建网络服务端
try {
ServerSocket serverSocket = new ServerSocket();
// 绑定指定的端口
serverSocket.bind(new InetSocketAddress(port));
System.out.println("=============Supger RPC Server Starting ...... ================");
while (true){
poolExecutor.execute(new ServerTask(serverSocket.accept()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 创建客户端请求处理的线程类
*/
private class ServerTask implements Runnable{
private final Socket socket;
private ServerTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try(
ObjectInputStream deSerializer = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream serializer = new ObjectOutputStream(socket.getOutputStream())
){
// 反序列化获取客户端传入的数据
RequestProtocol requestProtocol = (RequestProtocol) deSerializer.readObject();
// 获取接口全名称
String interfaceClassName = requestProtocol.getInterfaceClassName();
Object instance = serverMap.get(interfaceClassName);
if (null == instance){
return;
}
// 创建一个方法对象(反射方式)
Method method = instance.getClass()
.getDeclaredMethod(requestProtocol.getMethodName(), requestProtocol.getParameterTypes());
// 调用方法
Object result = method.invoke(instance, requestProtocol.getParameterValues());
// 序列化调用结果
serializer.writeObject(result);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
2.2 user-api
在RPC系统中,远程过程调用需要遵循一套统一的API模块和统一的java-bean。于是,把对应的api和javabean抽取出来,形成一个单独的模块(项目)。远程过程调用,就基于这样的一套标准接口。这里仅举一个简单的例子,目的在于帮助理解RPC的原理及实现。代码如下所示:
package service;
/**
* 定义暴露给客户端的服务接口
*/
public interface UserService {
String addUserName(String name);
}
2.3 user-provider
在RPC系统中,服务提供者,即服务的提供方。需要实现提供服务的对应的接口。即实现2.2 user-api中UserService接口。实现类代码如下所示。
package com.supger.service;
import service.UserService;
public class UserServiceImpl implements UserService {
@Override
public String addUserName(String name) {
return "添加的姓名为:"+name;
}
}
需要强调的是,该处实现的接口,需要引入公共的接口。
在实现接口之后,需要启动对应的服务,对应的代码如下所示。
package com.supger;
import com.supger.rpc.core.server.RpcServer;
import com.supger.service.UserServiceImpl;
import service.UserService;
public class App {
public static void main(String[] args) {
// 创建一个RPC的服务端
RpcServer rpcServer = new RpcServer();
// 发布暴露服务
rpcServer.publishServiceAPI(UserService.class,new UserServiceImpl());
// 启动服务
rpcServer.start(12345);
}
}
在上述代码中,先创建一个RPC的服务端。然后发布暴露对应的服务。最后启动服务。
2.4 user-consumer
在完成上述步骤之后,最后创建一个服务的消费者,在消费者中,通过调用RPC框架。完成对应的远程过程调用,就像调用本地方法一样简单,对应的代码如下所示。
package com.supger;
import com.supger.rpc.core.client.RpcClient;
import service.UserService;
import java.net.InetSocketAddress;
public class App {
public static void main(String[] args) {
// 在客户端调用RPC框架
UserService userService = RpcClient.getRemoteProxy(UserService.class, new InetSocketAddress("127.0.0.1", 12345));
String result = userService.addUserName("supger");
System.out.println(result);
}
}
至此,完成了实现自己的RPC框架的的流程。
3.运行测试
启动服务提供模块,如下图所示,表明服务已成功启动。
启动服务消费模块,如下图所示。
从运行结果可知,通过以上过程,实现了自己的RPC框架。
4. 结语
该过程涉及到Java中的一些基本的知识,具体包括:反射、序列化流、动态代理、多线程、线程池。后续会对相关概念做一个详尽的总结,目的一方面在于打好基本功,同时方便各位学习交流之用。
上述的实现过程,仅作为对RPC原理认识的一个初步方案。后续会学习RPC实现的相关框架Dubbo,等。届时,会进一步加深对RPC系统需要考虑的多方面的因素进行探讨。
关于RPC探讨的文章有很多,以下列出了一些参考文章。从不同的文章出发,会有不同的收获。从而加深对RPC进一步的理解。(本文为基于参考资源1的整理,特此注明参考来源)