RPC

solved problem

RPC mainly aims to solve two problems:

  • Solve the problem of calling between services in a distributed system.
  • When making a remote call, it must be as convenient as a local call, so that the caller cannot perceive the logic of the remote call.

In this section, let's learn how to implement the simplest rpc call based on websocket, and then we will implement a version based on netty4.

Open source address: https://github.com/houbb/rpc

Complete process

2018-08-24-rpc.png

The Client on the left corresponds to Service A in front, and the Server on the right corresponds to Service B.

Let's explain in detail step by step below.

  1. In the application layer code of Service A, the add method of an implementation class of Calculator is called, hoping to perform an addition operation;
  2. This Calculator implementation class does not directly implement the calculator’s addition, subtraction, multiplication, and division logic internally, but instead obtains the result of the operation by remotely calling the RPC interface of Service B, so it is called Stub;
  3. How does Stub establish remote communication with Service B? At this time, you need to use a remote communication tool, which is the Run-time Library in the figure. This tool will help you realize the function of remote communication. For example, Java's Socket is such a library. Of course, you can also use Http-based Protocol HttpClient, or other communication tools, can be used, RPC does not specify which protocol you want to use for communication;
  4. Stub establishes communication with Service B by calling the method provided by the communication tool, and then sends the request data to Service B. It should be noted that since the underlying network communication is based on a binary format, the data sent by the stub to the communication tool must also be binary, such as calculator.add(1,2), you must put the parameter values 1 and 2 Into a Request object (this Request object of course not only contains this information, but also includes other information such as which RPC interface of which service is to be called), and then serialize it to binary, and then pass it to the communication tool class. This will also be in the following code Reflected in realization;
  5. The binary data is transmitted to Service B. Of course, Service B also has its own communication tool, which receives binary requests through this communication tool;
  6. Since the data is binary, it is natural to deserialize the binary data into the request object, and then hand the request object to the Stub of Service B for processing;
  7. Like the Stub of Service A before, the Stub here is also a "fake thing". It is only responsible for parsing the request object, knowing which RPC interface the caller wants to call, and what are the incoming parameters. Then pass these parameters to the corresponding RPC interface, which is the actual implementation class of Calculator for execution. Obviously, if it is Java, then reflection must be used here.
  8. After the RPC interface is executed, the execution result is returned. Now it is Service B's turn to send the data to Service A. How to send it? The same reason, the same process, but now Service B becomes Client and Service A becomes Server: Service B deserialization execution result -> transfer to Service A -> Service A deserialization execution result -> will The result is returned to Application, complete.

Simple implementation

Suppose service A wants to call a method of service B.

Because they are not in the same memory, they cannot be used directly. How can a function similar to Dubbo be realized?

There is no need to use HTTP-level communication here, just use the TCP protocol.

common

Common modules define common objects.

  • Rpc constant
public interface RpcConstant {

    /**
     * 地址
     */
    String ADDRESS = "127.0.0.1";

    /**
     * 端口号
     */
    int PORT = 12345;

}
  • Request for participation
public class RpcCalculateRequest implements Serializable {

    private static final long serialVersionUID = 6420751004355300996L;

    /**
     * 参数一
     */
    private int one;

    /**
     * 参数二
     */
    private int two;

    //getter & setter & toString()
}
  • Service interface
public interface Calculator {

    /**
     * 计算加法
     * @param one 参数一
     * @param two 参数二
     * @return 返回结果
     */
    int add(int one, int two);

}

server

  • Implementation of the service interface
public class CalculatorImpl implements Calculator {

    @Override
    public int add(int one, int two) {
        return one + two;
    }

}
  • Start service
public static void main(String[] args) throws IOException {
    Calculator calculator = new CalculatorImpl();
    try (ServerSocket listener = new ServerSocket(RpcConstant.PORT)) {
        System.out.println("Server 端启动:" + RpcConstant.ADDRESS + ":" + RpcConstant.PORT);
        while (true) {
            try (Socket socket = listener.accept()) {
                // 将请求反序列化
                ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                Object object = objectInputStream.readObject();
                System.out.println("Request is: " + object);
                // 调用服务
                int result = 0;
                if (object instanceof RpcCalculateRequest) {
                    RpcCalculateRequest calculateRpcRequest = (RpcCalculateRequest) object;
                    result = calculator.add(calculateRpcRequest.getOne(), calculateRpcRequest.getTwo());
                }
                // 返回结果
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                objectOutputStream.writeObject(result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Start log:

Server 端启动:127.0.0.1:12345

client

  • Client call
public static void main(String[] args) {
    Calculator calculator = new CalculatorProxy();
    int result = calculator.add(1, 2);
    System.out.println(result);
}
  • Computed proxy class
public class CalculatorProxy implements Calculator {

    @Override
    public int add(int one, int two) {
        try {
            Socket socket = new Socket(RpcConstant.ADDRESS, RpcConstant.PORT);

            // 将请求序列化
            RpcCalculateRequest calculateRpcRequest = new RpcCalculateRequest(one, two);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());

            // 将请求发给服务提供方
            objectOutputStream.writeObject(calculateRpcRequest);

            // 将响应体反序列化
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            Object response = objectInputStream.readObject();

            if (response instanceof Integer) {
                return (Integer) response;
            } else {
                throw new RuntimeException();
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}
  • Call log

client side

3

server side

Server 端启动:127.0.0.1:12345
Request is: RpcCalculateRequest{one=1, two=2}

Open source address

In order to facilitate everyone to learn, the above source code has been open source:

https://github.com/houbb/rpc

I am an old horse, and I look forward to seeing you again next time.

SIGN.png


老马啸西风
191 声望34 粉丝