RPC是什么?

RPC(Remote Procedure Call)远程过程调用

RPC是通过什么实现啊?socket!

RPC(Remote Procedure Call,远程过程调用)是建立在Socket之上的,在一台机器上运行的主程序,可以调用另一台机器上准备好的子程序,就像LPC(本地过程调用).

越底层,代码越复杂、灵活性越高、效率越高;越上层,抽象封装的越好、代码越简单、效率越差。Socket和RPC的区别再次说明了这点。

RPC作为普遍的C/S开发方法,开发效率高效,可靠.但RPC方法的基本原则是--以模块调用的简单性忽略通讯的具体细节,以便程序员不用关心C/S之间的通讯协议,集中精力对付实现过程.这就决定了 RPC生成的通讯包不可能对每种应用都有最恰当的处理办法,与Socket方法相比,传输相同的有效数据,RPC占用更多的网络带宽.
  
RPC与socket的类比

两个老板手下各有一个负责接通MSN的秘书.这两个秘书就是基于RPC协议建立的会话层通信.老板不需要知道怎么使用MSN,只要告诉秘书,秘书就会通过MSN与对方建立会话请求和响应.

而基于Socket的通信,老板需要会使用MSN,这样尽管老板需要事先培训一下MSN的简单使用常识,但若与对方通信时,无需经过秘书,效率更高.

RPC的由来

为什么会有rpc这种东西呢?还是因为web服务技术架构的演变,最早以前,web应用都是单体架构,如果要在action里面用到service服务的话,直接在action代码中new一个service对象就可以获得service对象的属性,调用service对象的方法。

但是时代变了,由于单体架构不能支撑日益增长的流量,导致必须把单体架构拆为面向服务架构(SOA),经过这么一折腾,action和service两个类可能就不在一个JVM中了,也不在一台服务器上了。但是不变的是,action还是需要调用service中的方法呀,这怎么办呢? 这时候就需要一个工具能让我们通过网络远程调用另一台服务器上的service方法。这个工具就是RPC框架。简单的理解是一个节点请求另一个节点提供的服务

为什么不用Http协议传输数据?

由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。

RPC远程调用的流程
  1. 客户端告诉服务器,需要调用的函数ID(这里函数和进程ID存在一个映射,存在Map中
  2. 将调用函数的ID、入参(序列化,并转换为字节流)传送给服务器
  3. 服务端接受序列化数据,并反序列化为对象
  4. 服务器根据函数ID执行对应函数,进行逻辑处理
  5. 服务器将返回结果序列化并发送给客户端
  6. 客户端处理返回给浏览器
远程调用带来的问题

在远程调用时,我们需要执行的函数体是在远程的机器上的,这就带来了几个新问题:

  1. Call ID映射。我们怎么告诉远程机器我们要调用哪个方法呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用一个函数,编译器就自动帮我们调用它相应的函数指针。但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 <–> Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
  2. 序列化和反序列化。客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
  3. 网络传输。远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2。Java的Netty也属于这层的东西。
// Client端 
//    Student student = Call(ServerAddr, addAge, student)
4. 将这个调用映射为Call ID。
5. 将Call ID,student(params)序列化,以二进制形式打包
6. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
7. 等待服务器返回结果
8. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
9. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
10. 等待服务端请求
11. 得到一个请求后,将其数据包反序列化,得到Call ID
12. 通过在callIdMap中查找,得到相应的函数指针
13. 将student(params)反序列化后,在本地调用study()函数,得到结果
14. 将student结果序列化后通过网络返回给Client
代码实现RPC通信

先看目录结构

grpc 关闭request流_grpc 关闭request流

  • order-api 是微服务之间公共的接口定义
  • order-provide 是订单系统微服务
  • user-service 是用户系统微服务

现在user服务要调用order服务中的方法,代码展示


公共接口

IOrderService

public interface IOrderService {

    String queryOrderList();

    String orderById(String id);
}

RpcRequest

package com.xhc.example;

import java.io.Serializable;


public class RpcRequest implements Serializable{

    private String className;
    private String methodName;
    private Object[] args;
    private Class[] types;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getArgs() {
        return args;
    }

    public void setArgs(Object[] args) {
        this.args = args;
    }

    public Class[] getTypes() {
        return types;
    }

    public void setTypes(Class[] types) {
        this.types = types;
    }
}

订单中心
Bootstrap

package com.xhc.example;


public class Bootstrap {


    public static void main(String[] args) {
        //SPRING BOOT
        IOrderService orderService=new OrderServiceImpl();
        RpcProxyServer rpcProxyServer=new RpcProxyServer();
        rpcProxyServer.publisher(orderService,8080);
    }
}

OrderServiceImpl

package com.xhc.example;


public class OrderServiceImpl implements IOrderService{

    @Override
    public String queryOrderList() {
        return "我是 queryOrderList 方法,是的没错!";
    }

    @Override
    public String orderById(String id) {
        return "我是 orderById 方法,是的没错!";
    }
}

ProcessorHandler

package com.xhc.example;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;


public class ProcessorHandler implements Runnable{

    private Socket socket;
    private Object service;

    public ProcessorHandler(Socket socket, Object service) {
        this.socket = socket;
        this.service = service;
    }

    @Override
    public void run() {
        ObjectInputStream inputStream=null;
        ObjectOutputStream outputStream=null;
        try {
            inputStream=new ObjectInputStream(socket.getInputStream());//?
            RpcRequest request=(RpcRequest)inputStream.readObject(); //反序列化
            Object rs=invoke(request);
            System.out.println("服务端的执行结果:"+rs);
            outputStream=new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(rs);
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //TODO 关闭流
            if(inputStream!=null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Object invoke(RpcRequest request) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //通过反射进行服务的调用
        Class clazz=Class.forName(request.getClassName());
        //找到目标方法
        Method method=clazz.getMethod(request.getMethodName(),request.getTypes());
        return method.invoke(service,request.getArgs());
    }
}

RpcProxyServer

package com.xhc.example;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class RpcProxyServer {

    private final ExecutorService executorService= Executors.newCachedThreadPool();

    public void publisher(Object service,int port){
        ServerSocket serverSocket=null;
        try {
            serverSocket=new ServerSocket(port);
            while(true){
                Socket socket=serverSocket.accept(); //监听客户端请求
                executorService.execute(new ProcessorHandler(socket,service));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

用户中心
App

package com.xhc.example.rcp;

import com.xhc.example.IOrderService;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args ){
        RpcProxyClient rpcProxyClient=new RpcProxyClient();
        IOrderService orderService=rpcProxyClient.clientProxy(IOrderService.class,"localhost",8080);

        System.out.println(orderService.queryOrderList());
        System.out.println(orderService.orderById("Mic"));

    }
}

RemoteInvocationHandler

package com.xhc.example.rcp;

import com.xhc.example.RpcRequest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


public class RemoteInvocationHandler implements InvocationHandler{
    private String host;
    private int port;

    public RemoteInvocationHandler(String host, int port) {
        this.host = host;
        this.port = port;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //先建立远程连接
        RpcNetTransport rpcNetTransport=new RpcNetTransport(host,port);
        //传递数据了?
        // 调用哪个接口、 哪个方法、方法的参数?
        RpcRequest request=new RpcRequest();
        request.setArgs(args);
        request.setClassName(method.getDeclaringClass().getName());
        request.setTypes(method.getParameterTypes()); //参数的类型
        request.setMethodName(method.getName());
        return rpcNetTransport.send(request);
    }
}

RpcNetTransport

package com.xhc.example.rcp;

import com.xhc.example.RpcRequest;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;


public class RpcNetTransport {

    private String host;
    private int port;

    public RpcNetTransport(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public Socket newSocket() throws IOException {
        Socket socket=new Socket(host,port);
        return socket;
    }

    public Object send(RpcRequest request){
        ObjectOutputStream outputStream=null;
        ObjectInputStream inputStream=null;
        try {
            Socket socket=newSocket();
            //IO操作
            outputStream=new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(request);
            outputStream.flush();
            inputStream=new ObjectInputStream(socket.getInputStream());
            return inputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            
        }
        return null;
    }
}

RpcProxyClient

package com.xhc.example.rcp;

import java.lang.reflect.Proxy;


public class RpcProxyClient {

    public <T> T clientProxy(final Class<T> interfaceCls,final String host,final int port){
        return (T) Proxy.newProxyInstance(interfaceCls.getClassLoader(), new Class<?>[]{interfaceCls},new RemoteInvocationHandler(host,port));
    }
}