随着微服务的兴起,PRC成为了微服务中不可缺少的一部分,作为开发人员,我们不仅仅需要掌握RPC框架的使用,更需要去理解RPC框架的底层实现原理。请点击这里,代码已经上传到GitHub,欢迎一起学习探讨。
RPC即Remote Procedure Call(远程过程调用),在之前我们开发的单体项目中,项目中的一个方法可能依赖于项目中的另外一个方法,但都在一台服务器中,比较容易理解,实现起来也比较简单。但这种思想开发的项目,非常不利于后期项目的维护和扩展,也常常受到单台服务器性能的影响。为此,我们需要学习PRC框架,接受一种新的开发模式,它需要我们将之前的单体引用进行分解,这些分解的应用可能会单独的部署到某一台独立的服务器中,此时我们就需要利用RPC框架将这些分布在不同服务器上的微服务联系起来,组成一个整体。对于访问应用的人来说,他并感觉不到背后的逻辑,和访问之前的单体应用没有什么区别。
降了一大推,其实就是分布式的知识,而分布式的实现就依赖于PRC框架
在实现RPC框架之前,需要先了解以下RPC框架的原理
对于client端来说,其需要调用远程的某一个方法,那么它就需要获取远程相应方法对应的一个实例,但是这个类又不在本地,该怎么实现呢?很容易就想到了动态代理的思想,可以想获取到远程对应方法的一个实例,通过这个实例,我们就可远程调用这个对应的方法了。那么问题又来了,就算获得到了一个代理的实例,还是和远程的方法产生不了联系啊。哈哈~ 两台计算机产生联系,自然就是网络通信啊。我们通过代理类中的invoke()方法,通过网络通信,来将要调用的方法,方法的参数传递过去。最后在通过网络通信,将远程server端返回的结果取回,就完成了RPC的过程。
server端的实现就比较简单了,需要注册对外访问的接口,然后监听某个端口,等待连接之后,解析客户端传来的参数,然后调用本地的对应的方法,将产生的结果返回给客户端。
这里的协议其实就是一种约定,本意上和我们常见的http协议没有什么区别,规定在网络中如何传递参数,方便server端对client传过来的数据进行解析。
用于规定传递的参数格式,这里需要注意的是,由于这个类需要在网络中传输,那么就必须得实现serializable接口。
/**
* RPC请求协议类,需要实现序列化接口
* @author Time
* @created 2019/12/19
*/
public class RPCProtocol implements Serializable {
// 接口的全类名
String interfaceClassName;
// 方法名称
String methodName;
// 参数类型数组
Class>[] parameterTypes;
// 参数对象数组
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;
}
}
客户端实现的思想上面已经讲过了,主要就是创建一个动态代理类,然后通过网络通信传递参数和获取返回结果。
/**
* RPC框架核心实现类
* @author Time
* @created 2019/12/19
*/
public class RPCClient {
/**
* 通过动态代理获取到远程接口的具体实例,通过执行这个实例返回远程调用的结果
* @param
* @return
*/
public static T getRemoteProxy(Class interfaceClass, InetSocketAddress inetAddress){
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(inetAddress);
// 获取输入输出流
try(
ObjectOutputStream serialize = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream deSerialize = new ObjectInputStream(socket.getInputStream());
){
// 创建一个rpc框架的请求协议对象
RPCProtocol rpcProtocol = new RPCProtocol();
// 填充属性
rpcProtocol.setInterfaceClassName(interfaceClass.getName());
rpcProtocol.setMethodName(method.getName());
rpcProtocol.setParameterTypes(method.getParameterTypes());
rpcProtocol.setParameterValues(args);
// 协议对象序列化,进行网络传输
serialize.writeObject(rpcProtocol);
// 服务端生成的结果反序列化
Object result = deSerialize.readObject();
// 结果返回
return result;
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
});
}
}
这里通过创建一个线程池,可以处理多个客户端的请求。
/**
1. RPC框架核心服务类
2. 步骤:
3. 1 暴露调用接口
4. 2 启动服务
5. @author Time
6. @created 2019/12/19
*/
public class RPCServer {
// 定义存储暴露的接口
HashMap hashMap = new HashMap<>();
// 定义一个线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(8, 20, 200 , TimeUnit.MICROSECONDS,
new ArrayBlockingQueue(10)
);
// 暴露服务的接口
public void publicServiceAPI(Class> clazz, Object instance){
this.hashMap.put(clazz.getName(), instance);
}
// 发布服务的方法
public void start(int port){
// 创建网络服务端
try {
ServerSocket serverSocket = new ServerSocket();
//绑定端口哦
serverSocket.bind(new InetSocketAddress(port));
System.out.println("===================RPC Server服务端启动成功===================");
// 创建客户端处理请求的线程
while (true){
poolExecutor.execute(new ServerTask(serverSocket.accept()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
private class ServerTask implements Runnable{
private Socket socket;
public ServerTask(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try(
ObjectInputStream deserialize = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream serialize = new ObjectOutputStream(socket.getOutputStream())
){
// 反序列获取客户端的协议
RPCProtocol rpcProtocol = (RPCProtocol) deserialize.readObject();
// 通过客户端传来的值获取对应的接口
System.out.println(rpcProtocol.getInterfaceClassName());
Object instance = hashMap.get(rpcProtocol.getInterfaceClassName());
// 利用反射通过实例以及参数获取调用的具体方法
Method method = instance.getClass().getDeclaredMethod(rpcProtocol.getMethodName(),
rpcProtocol.getParameterTypes());
// 执行方法获取对应的结果
Object result = method.invoke(instance,rpcProtocol.getParameterValues());
// 将执行的结果序列化,传回给客户端
serialize.writeObject(result);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public class App
{
public static void main( String[] args )
{
// 获取服务端
RPCServer server = new RPCServer();
// 暴露接口
server.publicServiceAPI(UserService.class, new UserServiceImpl());
// 发布服务
server.start(8888);
}
}
public class App
{
public static void main( String[] args )
{
UserService userService = RPCClient.getRemoteProxy(UserService.class,new InetSocketAddress("127.0.0.1",8888));
String result = userService.addUserName("王五");
System.out.println(result);
}
}
总结:上面只是简单的实现,说明了其基本原理,代码已经上传到github。但目前流行的RPC框架则需要考虑更多的东西,如序列话、负载均衡、熔断器、动态监控等内容,有兴趣的话可以了解以下目前流行的RPC框架,如阿里的Double、新浪的Motan等。