Java explains the basic implementation of RPC

Posted by Smicks on Sat, 25 Apr 2020 11:37:21 +0200

Java explains the basic implementation of RPC

RPC remote procedure call can be said to be the basis of distributed system. In this paper, we will demonstrate what happens to a normal RPC call through Java.

I have seen people ask on the Internet why RPC is called remote procedure call instead of RMC remote method call. In my opinion, RPC is a reasonable way to call a process remotely, not necessarily a specific method. You only need to read the first version of the code to understand it.

The whole process can be summarized in one sentence: machine A establishes A connection with machine B through the network, A sends some parameters to B, B performs A certain process, and returns the result to A.

Let's start with a background. We have a commodity category

public class Product implements Serializable {

private Integer id;
private String name;

public Product(Integer id, String name) {
    this.id = id;
    this.name = name;
}

//toString()

//get set method

}
There is a commodity service interface

public interface IProductService {

Product getProductById(Integer id);

}
Implementation class with commodity service interface on the server

public class ProductServiceImpl implements IProductService {

@Override
public Product getProductById(Integer id) {
    //In fact, you should query the database to get data, which is simplified as follows
    return new Product(id, "Mobile phone");
}

}
Next, we send a commodity id to the server through the client. After the server obtains the id, it obtains the commodity information through the commodity service class and returns it to the client

public class Client {

public static void main(String[] args) throws Exception {
    //Create Socket
    Socket socket = new Socket("127.0.0.1", 8888);
    //Get output stream
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    //Transfer the commodity Id to the server through the network
    dos.writeInt(123);

    socket.getOutputStream().write(baos.toByteArray());
    socket.getOutputStream().flush();

    //Read the product information returned by the server
    DataInputStream dis = new DataInputStream(socket.getInputStream());
    Integer id = dis.readInt();     //Commodity id
    String name = dis.readUTF();    //Commodity name
    Product product = new Product(id, name);//Generate goods through the goods information returned by the server

    System.out.println(product);
    
    //To close stream resources for reading convenience, no try catch processing was done
    dos.close();
    baos.close();
    socket.close();
}

}

public class Server {

private static boolean running = true;

public static void main(String[] args) throws Exception {
    //Create server Socket
    ServerSocket ss = new ServerSocket(8888);
    //Constantly listening and processing client requests
    while (running) {
        Socket socket = ss.accept();
        process(socket);
        socket.close();
    }
    ss.close();
}

private static void process(Socket socket) throws Exception {
    InputStream is = socket.getInputStream();
    OutputStream os = socket.getOutputStream();
    DataInputStream dis = new DataInputStream(is);
    DataOutputStream dos = new DataOutputStream(os);

    //Read the id sent by the client
    Integer id = dis.readInt();
    //Call service class to generate goods
    IProductService service = new ProductServiceImpl();
    Product product = service.getProductById(id);
    //Write the product information back to the client
    dos.writeInt(id);
    dos.writeUTF(product.getName());
    dos.flush();

    dos.close();
    dis.close();
    os.close();
    is.close();
}

}
The above is RPC remote call version 1.0. You can see that the networking code is written in the client, and the network code is coupled with getProductById(). The actual RPC framework is absolutely impossible to do so.

In actual use, we will write a variety of remote calls. For example, the IProductService interface may be extended as follows:

public interface IProductService {

Product getProductById(Integer id);

Product getProductByName(String name);

Product getMostExpensiveProduct();

}
We can't write a piece of network connection code for each method. We have to think of a way to embed a common network connection code for all methods.

How to embed it? Here we can use the agent mode.

In Java, many excellent frameworks use proxy mode for code embedding, such as Mybatis. It embeds the code of the JDBC connection part around the sql statement through the proxy mode, so we focus on writing sql.

First, the server code needs to be modified:

public class Server {

private static boolean running = true;

public static void main(String[] args) throws Exception {
    //......
}

private static void process(Socket socket) throws Exception {
    //Get input stream, output stream
    InputStream is = socket.getInputStream();
    OutputStream os = socket.getOutputStream();
    ObjectInputStream ois = new ObjectInputStream(is);
    ObjectOutputStream oos = new ObjectOutputStream(os);
    //Get the method name of this remote call
    String methodName = ois.readUTF();
    //Get the parameter type of this remote call method
    Class[] parameterTypes = (Class[]) ois.readObject();
    //Get specific parameter object
    Object[] args = (Object[]) ois.readObject();
    
    //Create commodity service class instance
    IProductService service = new ProductServiceImpl();
    //Call the corresponding method according to the method name and parameters obtained remotely
    Method method = service.getClass().getMethod(methodName, parameterTypes);
    Product product = (Product) method.invoke(service, args);
    //Write the result back to the client
    oos.writeObject(product);

    oos.close();
    ois.close();
    socket.close();
}

}
Then on the client side, we create a new proxy class and provide a getStub method to get the proxy class. The dynamic proxy using JDK requires three parameters, one is the class loader, one is the class class of the interface, and the last is the InvocationHandler instance.

The logic behind the JDK dynamic proxy is as follows: the JVM will dynamically create a proxy class object according to the class class class of the interface, which implements the incoming interface, that is, it has the implementation of all methods in the interface. The specific implementation of the method can be specified by the user, that is, calling the invoke method of the InvocationHandler.

There are three parameters in the invoke method, namely, proxy proxy class, method called method and args called method. We can enhance the specific implementation method in the invoke method, which is the network call in this case.

public class Stub {

public static IProductService getStub() {

    InvocationHandler h = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //Establish Socket connection with server
            Socket socket = new Socket("127.0.0.1", 8888);
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            //Get the method name of the remote call
            String methodName = method.getName();
            //Get the parameter type of the remote call method
            Class[] parametersTypes = method.getParameterTypes();
            //Pass the method name to the server
            oos.writeUTF(methodName);
            //Pass the method parameter type to the server
            oos.writeObject(parametersTypes);
            //Pass method parameters to the server
            oos.writeObject(args);
            oos.flush();
            //Get the return result of the remote call
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Product product = (Product) ois.readObject();

            ois.close();
            oos.close();
            socket.close();
            return product;
        }
    };
    Object o = Proxy.newProxyInstance(IProductService.class.getClassLoader(), new Class[]{IProductService.class}, h);
    return (IProductService) o;
}

}
This new version is better than the first one, but it can still be optimized. Now our proxy can only return the implementation class of IProductService. We have to find a way to return any type of service implementation class.

The idea is similar to the remote call method. When we call a method remotely, we pass the name, parameter type and parameter of the method to the server. Now to create a service class dynamically, we can pass the name of the service interface to the server. After the server gets the name of the remote interface, it can find the corresponding service implementation class from the service registry.

As for how to register the Service implementation class to the Service registry, here is an idea: you can consider using spring's annotation injection. This is similar to our usual spring code. After creating the Service implementation class, we will annotate @ Service, so that we can traverse the Bean using @ Service and find the corresponding implementation class after receiving the remote call.

Reference: open course of evolution process of horse soldier rpc
https://www.bilibili.com/video/BV1Ug4y1875i?p=2

Original address https://www.cnblogs.com/tanshaoshenghao/p/12767414.html

Topics: Java socket network SQL