Implement your own RPC framework (study notes)

1. Basic concepts

RPC is the abbreviation of Remote Procedure Call, that is, remote procedure call. The most basic RPC model is shown in the figure below.
In the figure below, the service provider A Server and the service consumer B Server. Service consumers only need to use the interface to remotely call the implementation of the corresponding interface provided by the service provider, thereby obtaining the return value and completing the corresponding calling process.
Insert picture description here

2. Concrete realization

The following code example uses Socket to implement your own RPC framework. The project structure is as follows: The
Insert picture description here
project is divided into four modules: rpc module, public interface module, service provider module, and service consumer module.

2.1 rpc module

The rpc module mainly implements the function of RPC remote procedure call. This module is divided into three parts, client side, server side, and communication protocol. As shown below.
Insert picture description here

2.1.1 Communication protocol

In the RPC framework, the client method needs to provide the necessary information to call the method of the implementation class of the server corresponding interface through the interface. Specifically include: interface name, method name, parameter type list, parameter value list. The code is as follows:

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 Client

In the RPC framework, the client uses a dynamic proxy. In the dynamic proxy, the related information of the server interface that needs to be called is serialized, and finally sent to the server through Socket, waiting for the server to complete the call, and finally write the result Enter the Socket, inside the dynamic agent, and get the returned result. Return to the caller. The code is as follows:

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 Server

In the RPC framework, the server needs to be started to monitor the corresponding port. The client connects to the corresponding port. After the monitoring is started, when multiple client requests come, in order not to block because the previous request is not processed, it is necessary to introduce a multi-threaded mechanism. When the server monitors the client connection, it uses the thread pool to process the corresponding task. The corresponding Socket connection information obtained is stored in the task. Inside the task, in the run method, the corresponding call information of the client is obtained, and the corresponding implementation class is called through reflection. Before that, the server needs to expose the corresponding service list. The code is as follows:

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

In the RPC system, remote procedure calls need to follow a unified API module and a unified java-bean. Therefore, the corresponding api and javabean are extracted to form a separate module (project). Remote procedure calls are based on such a set of standard interfaces. Here is just a simple example to help understand the principle and implementation of RPC. The code is as follows:

package service;

/**
 * 定义暴露给客户端的服务接口
 */
public interface UserService {
    
    
    String addUserName(String name);
}

2.3 user-provider

In the RPC system, the service provider is the service provider. Need to implement the corresponding interface to provide services. That is to realize the UserService interface in 2.2 user-api. The implementation class code is shown below.

package com.supger.service;

import service.UserService;

public class UserServiceImpl implements UserService {
    
    
    @Override
    public String addUserName(String name) {
    
    
        return "添加的姓名为:"+name;
    }
}

It should be emphasized that the interface implemented here needs to introduce a public interface.
After implementing the interface, you need to start the corresponding service. The corresponding code is shown below.

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);
    }
}

In the above code, first create an RPC server. Then publish and expose the corresponding service. Finally start the service.

2.4 user-consumer

After completing the above steps, finally create a service consumer, in the consumer, by calling the RPC framework. Completing the corresponding remote procedure call is as simple as calling a local method. The corresponding code is shown below.

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);
    }
}

At this point, the process of implementing your own RPC framework is completed.

3. Run the test

Start the service providing module, as shown in the figure below, indicating that the service has been started successfully.
Insert picture description here
Start the service consumption module, as shown in the figure below.
Insert picture description here
It can be seen from the running results that through the above process, it has realized its own RPC framework.

4. Conclusion

This process involves some basic knowledge in Java, including: reflection, serialization flow, dynamic proxy, multithreading, thread pool. A detailed summary of related concepts will be made in the follow-up. On the one hand, the purpose is to do a good job of basic skills, and at the same time to facilitate your learning and communication.
The above-mentioned realization process is only a preliminary scheme for understanding the principle of RPC. Later, I will learn Dubbo, a related framework implemented by RPC, and so on. At that time, it will further deepen the discussion on the various factors that need to be considered in the RPC system.
There are many articles about RPC discussion, some reference articles are listed below. Starting from different articles, there will be different gains. So as to deepen the further understanding of RPC. (This article is based on the collation of reference resource 1, hereby indicate the reference source)

5. Reference resources

1. The original RPC framework principle is so learned, it is completely a kind of enjoyment!
2. Getting to know RPC 3. Basic principles of RPC

Guess you like

Origin blog.csdn.net/fanjianglin/article/details/112213296
Recommended