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;
}
}