RabbitMQ Getting Started: Remote Procedure Call (RPC)

Keywords: Java RabbitMQ

Suppose we want to call a remote method or function and wait for the result to be executed, which is what we usually call a Remote Procedure Call.What should I do?

Today we use RabbitMQ to implement a simple RPC system: the client sends a request message and the server responds with a response message.In order to receive a response, the client sends a callback queue to tell the server which queue the response message is sent to while sending the message.That is to say, each message has a callback queue, on which we change the definition of a callback queue as a class property, one queue for each client and one queue for requests from the same client.So the next question is, how do you know which queue the response messages in this queue belong to?

We use the correlationId, which generates a unique value for each request as the correlationId so that each time a response message comes, we go to the correlationId to determine which request's response message is and associate the request with the response.If you receive an unknown correlation Id, you can be sure that it is not a response to this client's request and discard it directly.

I. Working Model

  1. A unique callback queue is created when a client send is started.For a request, a message with two properties configured is sent: a callback queue (replay_to in the diagram) and a correlation.The request is sent to the rpc_queue queue and processed on the server side.
  2. The server waits for a request from the rpc_queue queue.When a request arrives, it starts working and returns the result by sending a message to the queue specified by replyTo.
  3. Clients will wait for the callback queue to return data.When the returned message arrives, it checks the correlation id property.If the property value matches the request, the response is returned to the program.

2. Code implementation

Next, look at the code implementation:

  1. Client
    public class RpcClient {
    
        Connection connection = null;
        Channel channel = null;
        //Callback Queue:Used to receive response messages from the server
        String queueName = "";
    
        // Definition RpcClient
        public RpcClient() throws IOException, TimeoutException {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            connection = factory.newConnection();
            channel = connection.createChannel();
            queueName = channel.queueDeclare().getQueue();
        }
    
        // Real Processing Logic
        public String call(String msg) throws IOException, InterruptedException {
            final String uuid = UUID.randomUUID().toString();
            //Subsequently, the server relies on"replyTo"To specify to which queue the return information will be written
            //Subsequently, the server identifies based on the Association"correlationId"To specify which request the response was returned
            AMQP.BasicProperties prop = new AMQP.BasicProperties().builder().replyTo(queueName).correlationId(uuid).build();
    
            channel.basicPublish("", RpcServer.QUEUE_NAME, prop, msg.getBytes());
            final BlockingQueue<String> blockQueue = new ArrayBlockingQueue<String>(1);
            channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope,
                        com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {
    
                    if (properties.getCorrelationId().equals(uuid)) {
                        String msg = new String(body, "UTF-8");
    
                        blockQueue.offer(msg);
                        System.out.println("**** rpc client reciver response :[" + msg + "]");
                    }
                }
    
            });
    
            return blockQueue.take();
        }
    
        //Close Connection
        public void close() throws IOException {
            connection.close();
        }
    
        public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
            RpcClient client = new RpcClient();
            client.call("4");
            client.close();
        }
    }

    When a request is sent, it is the producer; when a response is received, it is the consumer.

  2. Server
    public class RpcServer {
    
        //RPC Queue name
        public static final String QUEUE_NAME = "rpc_queue";
    
        //Fibonacci series, used to simulate work tasks
        public static int fib(int num) {
            if (num == 0)
                return 0;
            if (num == 1)
                return 1;
            return fib(num - 1) + fib(num - 2);
        }
    
        public static void main(String[] args) throws InterruptedException {
    
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = null;
            try {
                // 1.connection & channel
                connection = factory.newConnection();
                final Channel channel = connection.createChannel();
    
                // 2.declare queue
                channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    
                System.out.println("****** rpc server waiting for client request ......");
    
                // 3.Receive only one message at a time (task)
                channel.basicQos(1);
                //4.Get consumer instances
                Consumer consumer = new DefaultConsumer(channel) {
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                            byte[] body) throws IOException {
                        BasicProperties prop = new BasicProperties().builder().correlationId(properties.getCorrelationId())
                                .build();
                        String resp = "";
                        try {
                            String msg = new String(body, "UTF-8");
                            resp = fib(Integer.valueOf(msg)) + "";
                            System.out.println("*** will response to rpc client :" + resp);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        } finally {
                            channel.basicPublish("", properties.getReplyTo(), prop, resp.getBytes());
                            channel.basicAck(envelope.getDeliveryTag(), false);
                        }
    
                    }
                };
                // 5.Consumption messages (processing tasks)
                channel.basicConsume(QUEUE_NAME, false, consumer);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
    
    }

    When a request is accepted, it is the consumer; when a response is sent, it is the producer.

  3. Run the server and start waiting for requests

     

  4. Then run the client, console log:
    Server (one more print):
    ****** rpc server waiting for client request ......
    *** will response to rpc client :3
    
    Client:
    **** rpc client reciver response :[3]

     

3. Interludes

When I first wrote demo, instead of blocking the queue final BlockingQueue <String> blockQueue = new ArrayBlockingQueue <String> (1)) in the client, I wrote this directly:

@Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                    com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException {

                if (properties.getCorrelationId().equals(uuid)) {
                    String msg = new String(body, "UTF-8");

                    //blockQueue.offer(msg);
                    System.out.println("**** rpc client reciver response :[" + msg + "]");
                }
            }

The result was expected to be printed, but when run, it was found that it was not printed: **** RPC client reciver response: ["+ MSG +"] value.

The reason is that handleDelivery() is a method that runs in a child thread that continues to execute backwards until client.close() is executed; the method ends.

No results were printed because the main thread terminated.The problem is solved by adding a blocking queue and blocking the main thread without executing the close() method.

Posted by pkellum on Wed, 15 May 2019 20:24:23 -0700