JAVA Communications (2) - Implementing a Simple RPC Framework

Posted by paulieo10 on Tue, 21 May 2019 20:07:32 +0200

1. Introduction to RPC

RPC, fully known as Remote Procedure Call, or Remote Procedure Call, is a computer communication protocol.It allows remote services to be called as local services.It can be implemented in different ways.Examples include RMI (remote method call), Hessian, Http invoker, and so on.In addition, RPC is language independent.RPC Diagram

As shown in the figure above, assume that Computer1 is calling the sayHi() method, for Computer1, calling the sayHi() method is like calling a local method, calling -> returning.However, it can be seen from the subsequent calls that Computer1 calls the sayHi() method in Computer2. RPC masks the underlying implementation details, so that callers do not need to pay attention to network communication, data transmission and other details.

2. Implementation of RPC Framework

The core principle of RPC is described above: RPC enables local applications to easily and efficiently invoke processes (services) in the server.It is mainly used in distributed systems.Such as IPC components in Hadoop.But how do you implement an RPC framework?

Consider the following for reference only:

1. Communication model: Assuming that the communication is between A and B machines, there is a communication model between A and B, which is generally based on BIO or NIO in Java;

2. Process (Service) Location: Determine the specific process or method using the given communication method and determining the IP and port and method name;

3. Remote proxy objects: Locally invoked methods (services) are actually local proxies of remote methods, so a remote proxy object may be required. For Java, remote proxy objects can be implemented using dynamic objects in Java, encapsulating the invocation of remote method calls;

4. Serialization, the network transmission of object information such as object name, method name, parameter, etc. needs to be converted to binary transmission, where different serialization technical schemes may be required.For example: protobuf, Arvo, etc.

3. Implementing RPC Framework in Java

1. Implementing technical solutions
The RPC framework is implemented using a more primitive scheme, using Socket communication, dynamic proxy and reflection, and native Java serialization.

2. RPC Framework Architecture
The RPC architecture is divided into three parts:

1) Service providers, running on the server side, provide service interface definitions and service implementation classes.

2) Service Center, running on the server side, is responsible for publishing local services as remote services, managing remote services and providing them to service consumers.

3) Service consumers, running on clients, call remote services through remote proxy objects.

3. Specific implementation
The service provider interface definition and implementation are as follows:

public interface HelloService {

    String sayHi(String name);

}

HelloServices interface implementation class:

public class HelloServiceImpl implements HelloService {

    public String sayHi(String name) {
        return "Hi, " + name;
    }

}

Service Center code implementation, code as follows:

public interface Server {
    public void stop();

    public void start() throws IOException;

    public void register(Class serviceInterface, Class impl);

    public boolean isRunning();

    public int getPort();
}

Service Center Implementation Class:

public class ServiceCenter implements Server {
    private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private static final HashMap<String, Class> serviceRegistry = new HashMap<String, Class>();

    private static boolean isRunning = false;

    private static int port;

    public ServiceCenter(int port) {
        this.port = port;
    }

    public void stop() {
        isRunning = false;
        executor.shutdown();
    }

    public void start() throws IOException {
        ServerSocket server = new ServerSocket();
        server.bind(new InetSocketAddress(port));
        System.out.println("start server");
        try {
            while (true) {
                // 1. Monitor client's TCP connection, encapsulate it as a task after receiving it, and execute by thread pool
                executor.execute(new ServiceTask(server.accept()));
            }
        } finally {
            server.close();
        }
    }

    public void register(Class serviceInterface, Class impl) {
        serviceRegistry.put(serviceInterface.getName(), impl);
    }

    public boolean isRunning() {
        return isRunning;
    }

    public int getPort() {
        return port;
    }

    private static class ServiceTask implements Runnable {
        Socket clent = null;

        public ServiceTask(Socket client) {
            this.clent = client;
        }

        public void run() {
            ObjectInputStream input = null;
            ObjectOutputStream output = null;
            try {
                // 2. Deserialize the stream sent by the client into an object, reflect the caller to the service implementer, and get the execution result
                input = new ObjectInputStream(clent.getInputStream());
                String serviceName = input.readUTF();
                String methodName = input.readUTF();
                Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
                Object[] arguments = (Object[]) input.readObject();
                Class serviceClass = serviceRegistry.get(serviceName);
                if (serviceClass == null) {
                    throw new ClassNotFoundException(serviceName + " not found");
                }
                Method method = serviceClass.getMethod(methodName, parameterTypes);
                Object result = method.invoke(serviceClass.newInstance(), arguments);

                // 3. Deserialize the execution result and send it to client via socket
                output = new ObjectOutputStream(clent.getOutputStream());
                output.writeObject(result);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (output != null) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (clent != null) {
                    try {
                        clent.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }
}

Client's remote proxy object:

public class RPCClient<T> {
    public static <T> T getRemoteProxyObj(final Class<?> serviceInterface, final InetSocketAddress addr) {
        // 1. Convert local interface calls to JDK's dynamic proxy to implement remote interface calls in the dynamic proxy
        return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface},
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Socket socket = null;
                        ObjectOutputStream output = null;
                        ObjectInputStream input = null;
                        try {
                            // 2. Create a Socket client to connect to the remote service provider at the specified address
                            socket = new Socket();
                            socket.connect(addr);

                            // 3. Send to the service provider after encoding the interface class, method name, parameter list, etc., required for a remote service call
                            output = new ObjectOutputStream(socket.getOutputStream());
                            output.writeUTF(serviceInterface.getName());
                            output.writeUTF(method.getName());
                            output.writeObject(method.getParameterTypes());
                            output.writeObject(args);

                            // 4. Synchronization blocking waits for the server to return to answer, and returns after getting the answer
                            input = new ObjectInputStream(socket.getInputStream());
                            return input.readObject();
                        } finally {
                            if (socket != null) socket.close();
                            if (output != null) output.close();
                            if (input != null) input.close();
                        }
                    }
                });
    }
}

Finally, the test class:

public class RPCTest {

    public static void main(String[] args) throws IOException {
        new Thread(new Runnable() {
            public void run() {
                try {
                    Server serviceServer = new ServiceCenter(8088);
                    serviceServer.register(HelloService.class, HelloServiceImpl.class);
                    serviceServer.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        HelloService service = RPCClient.getRemoteProxyObj(HelloService.class, new InetSocketAddress("localhost", 8088));
        System.out.println(service.sayHi("test"));
    }
}

Run result:

regeist service HelloService
start server
Hi, test

4. Summary

  RPC is essentially a message processing model. RPC masks the communication details between different hosts at the bottom, allowing processes to invoke remote services as if they were local services.

5. Improvements

 The simple RPC framework implemented here is developed in the Java language and highly coupled with the Java language. Socket s used for communication are BIO-based, IO is not efficient, and Java's native serialization mechanism takes up too much memory and is not efficient.Consider several ways to improve.

The RPC framework based on JSON data transmission can be used.
You can use NIO or use Netty instead of BIO directly;
Use open source serialization mechanisms such as Hadoop Avro and Google protobuf;
Service registration can be managed using Zookeeper to make applications more stable.

------------------------—–

java Architect Project live, highly concurrent cluster distributed, large data highly available video tutorials, a total of 760G

Download address:

https://item.taobao.com/item.htm?id=555888526201

01. Senior Architect 42 Stages High
02.148 Hours of Java Advanced System Training Architecture Course
03.Java Advanced Internet Architect Course
04.Java Internet Architecture Netty, Nio, Mina, etc. - Video tutorials
05.Java Advanced Architecture Design 2016 Collation-Video Tutorial
06. Architect Foundation, Advanced Film
07.Java Architect Mandatory linux Operations and Maintenance Series
08.116 Hours of Java Advanced System Training Architecture Course
+
hadoop series of tutorials, java design patterns and data structures, Spring Cloud Micro Services, Getting Started with SpringBoot
------------------------—–

Topics: Java socket Hadoop network