RPC is introduced

Hi, my name is Jack Xu, and we know that Remote Procedure Call stands for RPC, which is a request for a service from a Remote computer program over the network. Invoking a service on a remote machine is as smooth as invoking a local service.

The following is the evolution history of RPC. At first, IT was RMI, but the communication between Java and Java was limited, so it could not cross languages. And then there’s HTTP + XML, which is a webservice, which can be called across languages, but we know that XML is a big thing, and it takes up a lot of network resources; Next comes HTTP + JSON, which is lightweight and requires a lot of repetitive non-business code to write. Next comes the framework phase, Google’s GRPC, Facebook’s Thrift (now handed over to Apache), alibaba’s Dubbo, and finally SpringCloud’s Restful.

I should add that neither RPC nor HTTP is good, each has its merits. Essentially, it’s a choice between readability and efficiency, between generality and ease of use. It’s hard to say who will end up better.

Knowledge used in this paper:

Reflection of the Java Core Foundation

The Agent Model to Help us find rent and Buy a House

Custom Annotations to the Java Core Foundation

“Understanding thread Pools from the root”

These articles are also written by me, interested partners can read.

The flow chart

This is a general flow chart of online, when requested, the caller by dynamic proxy, then serialize the request parameters, through the network to the called party, by the caller to get parameters, deserialize, then in the local reflection method is called, and then serialize the results of calculated returns to the caller, The call method deserializes the value. That’s the whole process.

The following is a flowchart of this handwritten RPC: the user initiates a request to access the client rpc-user-service service, and then the rpc-user-service invoks the server Rpc-order-service to query the order information. There is also a serialization and deserialization process.

Code implementation

The server RPC – order – service

Rpc-order-service, which is a Maven project, is a parent POM. Then create two sub-projects, order-API and Order-Provider, which are also Maven projects. The project structure is as follows.

order-api

The order-API is the contract that defines the interface, and the Order-Provider needs to implement it. Then print it into a JAR and upload it to the Nexus server, because rPC-user-service also needs to reference it and invoke the contract provided by the Order service.

When rpc-user-service requests rPC-order-service, the RpcRequest class tells order which method of which class to call and what parameters to pass in. Here I did not build a private server, the general company has a private server, in their own computer with install to the maven local warehouse can be.

@Data
public class RpcRequest implements Serializable {

    private String className;

    private String methodName;

    private Object[] args;


}
Copy the code

order-provider

Let’s take a look at the classes in the project, there are many, and then we’ll look at each of them.

The first is the service layer implementation contract. Since it is an implementation, let’s refer to the POM of the order-API.

< the dependency > < groupId > com. Jack < / groupId > < artifactId > order - API < / artifactId > < version > 1.0 - the SNAPSHOT < / version > </dependency>Copy the code

The implementation class OrderServiceImpl class

// When the annotation bean is loaded, it will save the bean information to the hash table
@JackRemoteService
public class OrderServiceImpl implements IOrderService {

    @Override
    public String queryOrderList(a) {
        return "this is rpc-order-service queryOrderList method";
    }

    @Override
    public String orderById(String id) {
        return "this is rpc-order-service orderById method,param is "+ id; }}Copy the code

A custom annotation @jackRemoteservice is used to save the bean’s information in the hash table when the bean is loaded for later reflection calls.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface JackRemoteService {

}
Copy the code

Annotation is a marking function, marking needs someone to identify it. There is need to implement the BeanPostProcessor interface, rewrite inside postProcessAfterInitialization method. What this method does is check whether the loaded bean has the JackRemoteService annotation, and if so, add all the methods in the bean to the hash table.

/ * * *@authorJackxu * saves the bean information to the hash table */ after the bean is loaded
@Component
public class InitialMerdiator implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (bean.getClass().isAnnotationPresent(JackRemoteService.class)) {
            Method[] methods = bean.getClass().getDeclaredMethods();
            for (Method method : methods) {
                // Interface name. Method name
                String key = bean.getClass().getInterfaces()[0].getName() + "." + method.getName();
                BeanInfo beanInfo = newBeanInfo(); beanInfo.setBean(bean); beanInfo.setMethod(method); Mediator.getInstance().put(key, beanInfo); }}returnbean; }}Copy the code

The hash table is defined as Mediator.class, with key being the class name. The method name

public class Mediator {

    public Map<String, BeanInfo> map = new ConcurrentHashMap<>();

    private Mediator(a) {}private static volatile Mediator instance;


    public static Mediator getInstance(a) {
        if (instance == null) {
            synchronized (Mediator.class) {
                if (instance == null) {
                    instance = newMediator(); }}}return instance;
    }

    public Map<String, BeanInfo> getMap(a) {
        return map;
    }

    public void put(String key, BeanInfo beanInfo) { map.put(key, beanInfo); }}Copy the code

Finally, after all beans have been loaded, start a socket listener, so the server is written and waiting for the client request.

Spring has built-in events that emit certain event actions when certain operations are completed. For example, listen for the ContextRefreshedEvent event, which will be triggered when all beans are initialized and successfully loaded. The ApplicationListener < ContextRefreshedEvent > interface can receive the listener action. Then write your own logic.

SocketServerInitial.class

// After the Spring container is started, a ContextRefreshedEvent is released
@Component
public class SocketServerInitial implements ApplicationListener<ContextRefreshedEvent> {
    / / thread pool
    private final ExecutorService executorService = new ThreadPoolExecutor(5.10.0L,
            TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // Start the service
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(8888);
            while (true) {
                Socket socket = serverSocket.accept();
                executorService.execute(newProcessorHandler(socket)); }}catch (Exception e) {
            e.printStackTrace();
        } finally {
            / / close the socket
            if(serverSocket ! =null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
Copy the code

Thread pool execution method, is to receive the socket request, first the RpcRequest deserialization, and then according to the passed interface, method in the hash table to find the method, and then through reflection call, and finally return the result.

/ * * *@author jack xu
 */
public class ProcessorHandler implements Runnable {

    private Socket socket;

    public ProcessorHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run(a) {
        ObjectOutputStream outputStream = null;
        ObjectInputStream inputStream = null;
        try {
            inputStream = new ObjectInputStream(socket.getInputStream());
            // Deserialize
            RpcRequest request = (RpcRequest) inputStream.readObject();
            // Execute the method according to the passed parameters
            System.out.println("request :" + request);
            Object result = processor(request);
            System.out.println("response :" + result);
            // Write the result to the output stream
            outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(result);
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            / / close the flow
            if(inputStream ! =null) {
                try {
                    inputStream.close();
                } catch(IOException e) { e.printStackTrace(); }}if(outputStream ! =null) {
                try {
                    outputStream.close();
                } catch(IOException e) { e.printStackTrace(); }}}}public Object processor(RpcRequest request) {
        try {
            Map<String, BeanInfo> map = Mediator.getInstance().getMap();
            // Interface name. Method name
            String key = request.getClassName() + "." + request.getMethodName();
            // How to take it out
            BeanInfo beanInfo = map.get(key);
            if (beanInfo == null) {
                return null;
            }
            / / bean object
            Object bean = beanInfo.getBean();
            / / method
            Method method = beanInfo.getMethod();
            / / reflection
            return method.invoke(bean, request.getArgs());
        } catch (Exception e) {
            e.printStackTrace();
            return null; }}}Copy the code

In BIO transmission mode, one request must be completed before the next request can be executed, which will lead to low efficiency. Therefore, thread pool is adopted to solve this problem, but if the request is very large, there will still be congestion. The best way is to implement RPC in netty mode.

The client RPC – user – service

Rpc-user-service is a Spring Boot project, because eventually we will call it restful, and if it is too slow to build with SSM, let’s look at the overall structure of the project first.

Let’s start with the Controller layer. First, we reference the interface order-API. Since we have installed the maven repository locally, we can refer to the POM directly.

< the dependency > < groupId > com. Jack < / groupId > < artifactId > order - API < / artifactId > < version > 1.0 - the SNAPSHOT < / version > </dependency>Copy the code
@RestController
public class UserController {

    // The function here is to encapsulate the interface as a proxy object
    @JackReference
    private IOrderService orderService;

    @JackReference
    private IGoodService goodService;

    @GetMapping("/test")
    public String test(a) {
        return orderService.queryOrderList();
    }

    @GetMapping("/get")
    public String get(a) {
        return goodService.getGoodInfoById(1L); }}Copy the code

We see that there is also a custom annotation here, JackReference, which turns the annotated interface into a proxy object.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface JackReference {

}
Copy the code

We are still in gourd ladle, when beans before loading, here is a method that postProcessBeforeInitialization set JackReference annotations on interface to proxy objects.

@Component
public class ReferenceInvokeProxy implements BeanPostProcessor {

    @Autowired
    RemoteInvocationHandler invocationHandler;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        // Get all fields
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(JackReference.class)) {
                field.setAccessible(true);
                Object proxy = Proxy.newProxyInstance(field.getType().getClassLoader(), newClass<? >[]{field.getType()}, invocationHandler);try {
                    field.set(bean, proxy);
                } catch(IllegalAccessException e) { e.printStackTrace(); }}}returnbean; }}Copy the code

We know the orderService. QueryOrderList () we are not the instance on the local, can perform, so did it is to be performed in a proxy object method, the parameters of the encapsulated into RpcRequest, then sent via the Socket to the server, and then get the returned data, To make it look like we’re executing locally, the proxy object is actually doing a lot of the work for us.

@Component
public class RemoteInvocationHandler implements InvocationHandler {

    @Value("${rpc.host}")
    private String host;

    @Value("${rpc.port}")
    private int port;


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        RpcRequest request = new RpcRequest();
        request.setArgs(args);
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        return send(request);
    }

    public Object send(RpcRequest request) {
        ObjectOutputStream outputStream = null;
        ObjectInputStream inputStream = null;
        try {
            Socket socket = new Socket(host, port);
            / / IO operations
            outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(request);
            outputStream.flush();
            inputStream = new ObjectInputStream(socket.getInputStream());
            return inputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            / / close the flow
            if(inputStream ! =null) {
                try {
                    inputStream.close();
                } catch(IOException e) { e.printStackTrace(); }}if(outputStream ! =null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
Copy the code

test

Start the server first, the server code is written like this, need to add ComponentScan scan package

/ * * *@author jack xu
 */
@Configuration
@ComponentScan("com.jack")
public class Bootstrap {

    public static void main(String[] args) {
        ApplicationContext applicationContext = newAnnotationConfigApplicationContext(Bootstrap.class); }}Copy the code

Already running, waiting for client request

The client is a Spring Boot project and can be started normally

@SpringBootApplication
public class RpcUserServiceApplication {

    public static void main(String[] args) { SpringApplication.run(RpcUserServiceApplication.class, args); }}Copy the code

He’s running, tooOpen the browser to visit next, got the result successfully

The server also prints the corresponding log, and a complete RPC request is completed.

At the end

The source code for this article is available on Github

rpc-user-service

rpc-order-service

So the last thing we’re going to do here is we’re going to do multithreaded +BIO, but if you’re interested you can do Netty. In this article, we only discuss the communication between the two services, thanks for watching ~