假如我们想要调用远程的一个方法或函数并等待执行结果,也就是我们通常说的远程过程调用(Remote Procedure Call)。怎么办?
今天我们就用RabbitMQ来实现一个简单的RPC系统:客户端发送一个请求消息,服务端以一个响应消息回应。为了能够接收到响应,客户端在发送消息的同时发送一个回调队列用来告诉服务端响应消息发送到哪个队列里面。也就是说每个消息一个回调队列,在此基础上我们变下,将回调队列定义成类的属性,这个每个客户端一个队列,同一个客户端的请求共用一个队列。那么接下来有个问题,怎么知道这个队列里面的响应消息是属于哪个队列的呢?
我们会用到关联标识(correlationId),每个请求我们都会生成一个唯一的值作为correlationId,这样每次有响应消息来的时候,我们就去看correlationId来确定到底是哪个请求的响应消息,将请求和响应关联起来。如果收到一个不知道的correlationId,就可以确定不是这个客户端的请求的响应,可以直接丢弃掉。
一、工作模型
- 客户端发送启动后,会创建独特的回调队列。对于一个请求发送配置了两个属性的消息:一个是回调队列(图中的replay_to),一个是correlation_id。 这个请求会发送到rpc_queue队列,然后到达服务端处理。
- 服务端等待rpc_queue队列的请求。当有请求到来时,它就会开始干活并将结果通过发送消息来返回,该返回消息发送到replay_to指定的队列。
- 客户端将等待回调队列返回数据。当返回的消息到达时,它将检查correlation_id属性。如果该属性值和请求匹配,就将响应返回给程序。
二、代码实现
客户端:发送请求的时候,它是生产者;接受响应的时候,它是消费者。
package com.bj.rabbitmq.study.five;
import java.io.IOException;
import java.util.UUID;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;
public class RPCSend {
//用于客户端发送消息到服务器端的队列
private static final String RPC_QUEUE_NAME = "rpc_queue";
public static void main(String[] args) throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//回调队列,服务器端给客户端返回响应的队列
String responseQueue = channel.queueDeclare().getQueue();
//每个请求的唯一标志,请求ID,让客户端区分得到的响应属于哪次请求
String correlationId = UUID.randomUUID().toString() ;
//给服务端指定回调队列名称和请求ID
BasicProperties props = new BasicProperties.Builder().replyTo(responseQueue)
.correlationId(correlationId).build();
String msg="RPC";
channel.basicPublish("", RPC_QUEUE_NAME, props, msg.getBytes());
System.out.println(" [client] Sent '"+msg+"'");
QueueingConsumer consumer = new QueueingConsumer(channel);
//客户端在回调队列上监听消息
channel.basicConsume(responseQueue, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
if (delivery.getProperties().getCorrelationId().equals(correlationId)) {
String result = new String(delivery.getBody());
System.out.println(" [client] Got '"+result+"'");
}
}
}
}
服务端:接受请求的时候,它是消费者;发送响应的时候,它是生产者。
package com.bj.rabbitmq.study.five;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.QueueingConsumer.Delivery;
import com.rabbitmq.client.ShutdownSignalException;
public class RPCRecv {
//用于客户端发送消息到服务器端的队列
private static final String RPC_QUEUE_NAME = "rpc_queue";
public static String sayHello(String name){
return "hello "+name;
}
public static void main(String[] args) throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null) ;
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(RPC_QUEUE_NAME, false , consumer) ;
while(true){
System.out.println("服务器端等待接收消息...");
Delivery delivery = consumer.nextDelivery();
String result=new String(delivery.getBody());
System.out.println("服务器端成功接收到消息:"+result);
BasicProperties props = delivery.getProperties();
BasicProperties responseProps = new BasicProperties.Builder() //给服务端指定回调队列名称和请求ID
.correlationId(props.getCorrelationId()) //请求ID
.build();
//将结果返回到客户端监听的Queue
channel.basicPublish("", props.getReplyTo() , responseProps , sayHello(result).getBytes() ) ;
System.out.println(" [SERVER] SENT '"+sayHello(result)+"'");
}
}
}
启动服务端,再启动客户端,输出记录如下
客户端-------------------------------------
[client] Sent 'RPC'
[client] Got 'hello RPC'
服务端-----------------------------------------
服务器端等待接收消息...
服务器端成功接收到消息:RPC
[SERVER] SENT 'hello RPC'
服务器端等待接收消息...
参考:
https://www.cnblogs.com/sam-uncle/category/1218141.html
https://blog.csdn.net/xiaoxian8023/column/info/rabbitmq-arron