1、RPC简述

       RPC,Remote Procedure Call 远程过程调用。通俗讲,两段程序不在同一个内存空间,无法直接通过方法名调用,就需要通过网络通信方式调用。对于RabbitMQ,本身就是用于消息通信。简单的RabbitMQ是,生产端发送消息,经由交换器,到达队列。消费端不需要知道生产端,消费端订阅队列,消费队列中的消息。而对于RPC,希望消费端消费消息后,返回一个结果,结果经由网络,再返回给生产端。

      不考虑RabbitMQ针对RPC的特有设计。最简单的设计是,生产端和消费端共同约定消费队列和回复队列,同时生产端每次发送消息时指定一个唯一ID。生产端将消息和唯一ID发送给消费队列,消费者从消费队列获取消息。处理后,将结果和生产端发送过来的唯一ID,发送给回复队列。生产端从回复队列获取消息和ID,判断ID是否匹配,匹配,则此消息为回复消息。

    以上实现的RPC存在问题:生产端和消费端需要约定回复队列,这就要求生产端和消费端互相知道,这无法实现解耦。解决方案:生产端在发送消息时,也将回复队列名称随消息一起发送给队列。

2、举例说明RabbitMQ中的RPC实现

相关要点,见源码注释

pom依赖

   <dependencies>
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>4.6.0</version>
        </dependency>
    </dependencies>

生产端,也就是客户端代码

 

 1 package test;
 2 
 3 import java.io.IOException;
 4 import java.util.UUID;
 5 
 6 import com.rabbitmq.client.AMQP;
 7 import com.rabbitmq.client.AMQP.BasicProperties;
 8 import com.rabbitmq.client.Channel;
 9 import com.rabbitmq.client.DefaultConsumer;
10 import com.rabbitmq.client.Envelope;
11 
12 import utils.ChannelUtils;
13 
14 public class RPCClient {
15 
16     public static void main(String[] args) throws IOException {
17         RPCClient rpcClient = new RPCClient();
18         rpcClient.client();
19     }
20 
21     public void client() throws IOException {
22         //此方法封装了如何连接RabbitMQ和创建connection,channel.源码见附录
23         Channel channel = ChannelUtils.getChannelInstance("client");
24         channel.exchangeDelete("exchange_rpc");
25         channel.exchangeDeclare("exchange_rpc", "direct", false, false, null);
26 
27         channel.queueDelete("queue_rpc");
28         channel.queueDeclare("queue_rpc", false, false, false, null);
29 
30         channel.queueBind("queue_rpc", "exchange_rpc", "rpc");
31 
32         //此处注意:我们声明了要回复的队列。队列名称由RabbitMQ自动创建。
33         //这样做的好处是:每个客户端有属于自己的唯一回复队列,生命周期同客户端
34         String replyQueue = channel.queueDeclare().getQueue();
35         final String corrID = UUID.randomUUID().toString();
36 
37         //这里我们设计三类消息。
38         //消息1:指定回复队列和ID
39         //消息2:仅指定回复队列
40         //消息3:不指定回复队列和ID
41         AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
42         // 指定回复队列和回复correlateId
43         builder.replyTo(replyQueue).correlationId(corrID);
44         AMQP.BasicProperties properties = builder.build();
45         for (int i = 0; i < 2; i++) {
46             channel.basicPublish("exchange_rpc", "rpc", properties,
47                     (System.currentTimeMillis() + "-rpc发送消息1").getBytes());
48         }
49 
50         AMQP.BasicProperties.Builder builder2 = new AMQP.BasicProperties.Builder();
51         // 指定回复队列,未指定回复correlateId
52         builder2.replyTo(replyQueue);
53         AMQP.BasicProperties properties2 = builder2.build();
54         for (int i = 0; i < 2; i++) {
55             channel.basicPublish("exchange_rpc", "rpc", properties2,
56                     (System.currentTimeMillis() + "-rpc发送消息2").getBytes());
57         }
58 
59         for (int i = 0; i < 2; i++) {
60             // 未指定回复队列和correlateId
61             channel.basicPublish("exchange_rpc", "rpc", null, (System.currentTimeMillis() + "-rpc发送消息3").getBytes());
62         }
63 
64         DefaultConsumer c = new DefaultConsumer(channel) {
65             //这是一个回调函数,客户端获取消息,就调用此方法,处理消息
66             @Override
67             public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
68                     throws IOException {
69                 if (corrID.equals(properties.getCorrelationId())) {
70                     System.out.println("correlationID对应上的消息:" + new String(body));
71                 } else {
72                     System.out.println("correlationID未对应上的消息:" + new String(body));
73                 }
74             }
75         };
76         channel.basicConsume(replyQueue, true, c);
77     }
78 
79 }

 

消费端,也就是服务器端代码

 1 package test;
 2 
 3 import java.io.IOException;
 4 
 5 import com.rabbitmq.client.AMQP;
 6 import com.rabbitmq.client.Channel;
 7 import com.rabbitmq.client.DefaultConsumer;
 8 import com.rabbitmq.client.Envelope;
 9 import com.rabbitmq.client.AMQP.BasicProperties;
10 
11 import utils.ChannelUtils;
12 
13 public class RPCServer {
14 
15     public static void main(String[] args) throws IOException {
16         RPCServer rpcServer = new RPCServer();
17         rpcServer.Server();
18     }
19 
20     public void Server() throws IOException {
21         final Channel channel = ChannelUtils.getChannelInstance("server");
22 
23         DefaultConsumer c = new DefaultConsumer(channel) {
24             
25             //这是一个回到函数,服务器端获取到消息,就会调用此方法处理消息
26             @Override
27             public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
28                     throws IOException {
29                 System.out.println(new String(body));
30 
31                 AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
32                 //我们在将要回复的消息属性中,放入从客户端传递过来的correlateId
33                 builder.correlationId(properties.getCorrelationId());
34                 AMQP.BasicProperties prop = builder.build();
35 
36                 //发送给回复队列的消息,exchange="",routingKey=回复队列名称
37                 //因为RabbitMQ对于队列,始终存在一个默认exchange="",routingKey=队列名称的绑定关系
38                 channel.basicPublish("", properties.getReplyTo(), prop, (new String(body) + "-回复").getBytes());
39 
40             }
41         };
42         channel.basicConsume("queue_rpc", true, c);
43     }
44 
45 }

 

 

附录:ChannelUtils 的源码

package utils;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class ChannelUtils {

    // AMQP的连接其实是对Socket做的封装, 注意以下AMQP协议的版本号,不同版本的协议用法可能不同。
    public static Channel getChannelInstance(String ConnectionDescription) {
        try {
            ConnectionFactory connectionFactory = getConnectionFactory();
            Connection connection = connectionFactory.newConnection(ConnectionDescription);

            return connection.createChannel();
        } catch (Exception e) {
            throw new RuntimeException("获取Channel连接失败");
        }

    }

    public static ConnectionFactory getConnectionFactory() {
        ConnectionFactory connectionFactory = new ConnectionFactory();

        connectionFactory.setHost("192.168.1.111");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("drs");
        connectionFactory.setPassword("123456");

        return connectionFactory;
    }

}