微服务-RPC原理篇

846 阅读5分钟

        一些成熟的RPC框架如dubbo/springcloud/grpc/HFS等等在分布式后台开发体系中提供了非常大的效率提升。最近遇到了几个关于dubbo使用的坑,发现团队对RPC框架的原理与框架代码不是十分深入了解, 应团队成员的要求,做一些关于RPC框架原理的讲解,包括了RPC框架的概念知识,框架使用到的技术栈,框架中常见的组件,常见的实现技术细节的分享。这几篇文章是这个分享系列的总结。

1. 概念知识

        RPC 是远程过程调用(Remote Procedure Call)的缩写,RPC 是指计算机 A 上的进程,调用另外一台计算机 B 上的进程,其中 A 上的调用进程被挂起,而 B 上的被调用进程开始执行,当值返回给 A 时,A 进程继续执行。调用方可以通过使用参数将信息传送给被调用方,而后可以通过传回的结果得到信息,原理如下所示。

2.相关技术栈

2.1 socket网络编程

        其中RPC通信中最底层的是socket网络通信,网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节, 支持TCP/UDP两种常见的网络协议通信;通常的Socket连接通信过程如下所示。

  • 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
  • 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直阻塞等待,直到客户端连接到服务器上给定的端口。
  • 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
  • Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
  • 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
  •  连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

其中使用到的一些Socket类方法如下所示。

方法描述

public ServerSocket(int port) throws IOException

创建绑定到特定端口的服务器套接字。

public Socket accept() throws IOException

ServerSocket 侦听并接受到此套接字的连接。

public void connect(SocketAddress host, int timeout) throws IOException

将此套接字连接到服务器,并指定一个超时值。

public InputStream getInputStream() throws IOException

返回此套接字的输入流。

public OutputStream getOutputStream() throws IOException

返回此套接字的输出流。

public void close() throws IOException

关闭此套接字。

2.2 动态代理

3.最简单的一个例子

       这里我们写一个RPC通信最简单的例子,使用动态代理、socket网络通信、反射等最基本的技术来实现一个远程调用的例子。其中消费端使用的是JDK的Proxy代理工具类,会生成一个通信客户端代理用来发送RPC请求信息(调用类名+方法名+参数),服务提供端使用线程池对每个接收的连接进行处理(BIO网络模型), 阻塞读取客户端Socket套接字中的RPC请求信息后,通过类名与方法名反射获得本地的执行方法对象,执行后得出结果并回传给服务消费端。其中RPC-服务提供端的代码如下所示。

public class RpcProviderMain {
    public static void main(String[] args) throws IOException {
        HelloService helloService = new HelloServiceImpl();
        ProviderProxy.provider(helloService, 8083);
    }
}

public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello() {
        System.out.println("hello - i ' m jack");
        return "hello";
    }
}


public class ProviderProxy {
    private static final ExecutorService executorService = Executors.newCachedThreadPool();
    public static void provider(final Object service, int port) throws IOException {
        //绑定端口
        ServerSocket serverSocket = new ServerSocket(port);
        //循环等待
        while (true) {
            //阻塞接收客户端连接
            final Socket socket = serverSocket.accept();
            //放入线程池对客户端连接套接字处理
            executorService.execute(() -> {
                try {
                     //获取客户端套接字输入流
                    ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                    try {
                        //阻塞读取套接字的字节流,获取方法名
                        String methodName = inputStream.readUTF();
                        //阻塞读取套接字的字节流,获取方法参数
                        Object[] ars = (Object[]) inputStream.readObject();
                         //获取客户端套接字的输出流
                        ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                        try {
                            //反射获取实现本地方法后执行并得到结果
                            Object result = MethodUtils.invokeMethod(service, methodName, ars);
                            //将结果写入套接字输出流中,这样客户端就可以收到了
                            outputStream.writeObject(result);
                        } catch (Throwable t) {
                            //出异常了,将异常信息写入客户端套接字的输出流中
                            outputStream.writeObject(t);
                        } finally {
                            //关闭输出流
                            outputStream.close();
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } finally {
                        //关闭输入流
                        inputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    //关闭套接字
                    socket.close();
                }
            });
        }
    }
}

       这样就完成了一个RPC服务提供端的初始化,那么下面就实现RPC-服务消费端如下所示。

public class RpcConsumeMain {
    
    public static void main(String[] args) throws InterruptedException {
        HelloService service = ConsumerProxy.cousume(HelloService.class, "127.0.0.1", 8083);
        String hello = service.sayHello();
    }
}

public class ConsumerProxy {

    private static InvocationHandler createInvocation(String host, Integer port) {
        return (proxy, method, args) -> {
            //建立服务器端连接套接字
            Socket socket = new Socket(host, port);
            try {
                //获取套接字的输出流
                ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                try {
                    //写入方法名
                    outputStream.writeUTF(method.getName());
                    //写入方法参数
                    outputStream.writeObject(args);
                    //获取套接字的输入流
                    ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                    try {
                         //阻塞等待读取套接字输入流数据
                        Object result = inputStream.readObject();
                        if (result instanceof Throwable) {
                            throw (Throwable) result;
                        }
                        return result;
                    } finally {
                        //关闭套接字输入流
                        inputStream.close();
                    }
                } finally {
                    //关闭套接字输出流
                    outputStream.close();
                }
            } finally {
                //关闭套接字
                socket.close();
            }
        };
    }
    
    public static  T cousume(final Class interfaceClass, final String host, final int port) {
        //获取接口类加载用的classloader
        ClassLoader classLoader = interfaceClass.getClassLoader();
        //创建InvocationHandler对象
        InvocationHandler invocationHandler = createInvocation(host, port);
        Class[] classes = new Class[]{interfaceClass};
        //新建一个代理后的对象
        return (T) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
    }
    
}

4. 总结

本篇主要讲述RPC的原理以及相关技术栈如socket网络编程,动态代理技术、反射技术等等。原理和例子虽然简单, 但却是目前常见RPC框架通用的基本原理,正所谓知其然知其所以然,先从RPC的基础原理学起,再去接触大的RPC框架会有事半功倍的效果。