这篇记录一下RabbitMQ如何实现RPC(远程过程调用)。
RPC : 我的理解是我们从远程计算机上的系统中运行某个功能并接收其运行结果,
使用RabbitMQ可以很容易实现。
介绍
代码为RabbitMQ官方示例代码。服务端中有一个计算斐波那契的方法,从客户端通过RabbitMQ传递一个数字,服务端中接收后代入斐波那契方法中计算结果,并将结果返回给客户端。
编写客户端
public class RPCClient {
private Connection connection;
private Channel channel;
private String requestQueueName = "rpc_queue";
private String replyQueueName;
public RPCClient() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connection = connectionFactory.newConnection();
channel = connection.createChannel();
//回调的队列名称
replyQueueName = channel.queueDeclare().getQueue();
System.out.println("回调队列名:" + replyQueueName);
}
public String call(String message) throws IOException, InterruptedException {
/*指定一个唯一标识,用于将RPC响应与请求关联起来*/
final String corrId = UUID.randomUUID().toString();
System.out.println("\ncorrId为:" +corrId);
/*定义消息属性,指定唯一标识corrId与回调队列*/
AMQP.BasicProperties props = new AMQP.BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
channel.basicPublish("",requestQueueName,props,message.getBytes());
final BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
channel.basicConsume(replyQueueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/*检查corrId是否匹配*/
if (properties.getCorrelationId().equals(corrId)){
response.offer(new String(body,"UTF-8"));
}
}
});
return response.take();
}
public void close() throws IOException {
connection.close();
}
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
RPCClient rpcClient = new RPCClient();
String resp = rpcClient.call("10");
System.out.println("调用远程方法返回的结果:" + resp);
if (rpcClient!=null){
rpcClient.close();
}
}
}
补充说明
在上面介绍的方法中,我们建议为每个RPC请求创建一个回调队列。这是非常低效的,但幸运的是有一个更好的方法 - 让我们为每个客户端创建一个回调队列。
这引发了一个新问题,在该队列中收到回复后,不清楚回复属于哪个请求。那是什么时候使用 correlationId属性。我们将把它设置为每个请求的唯一值。稍后,当我们在回调队列中收到消息时,我们会查看此属性,并基于此属性,我们将能够将响应与请求进行匹配。如果我们看到未知的 correlationId值,我们可以放心地丢弃该消息 - 它不属于我们的请求。
编写服务端
public class RPCServer {
private final static String RPC_QUEUE_NAME = "rpc_queue";
private static int fib(int n) {
if (n ==0) {
return 0;
}
if (n == 1) {
return 1;
}
return fib(n-1) + fib(n-2);
}
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = null;
try {
connection = connectionFactory.newConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME,false,false,false,null);
/*一次只处理一条消息*/
channel.basicQos(1);
final Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/*定义消息属性*/
AMQP.BasicProperties replyProps = new AMQP.BasicProperties
.Builder()
.correlationId(properties.getCorrelationId())
.build();
String response="";
String message = new String(body,"UTF-8");
System.out.println("收到的消息:" + message);
channel.basicAck(envelope.getDeliveryTag(),false);
int n = Integer.parseInt(message);
response += fib(n);
System.out.println("计算结果:" + response);
/*向回调队列发送结果*/
channel.basicPublish("",properties.getReplyTo(),replyProps,response.getBytes("UTF-8"));
synchronized (this){
this.notify();
}
}
};
/*消费消息*/
channel.basicConsume(RPC_QUEUE_NAME,false,consumer);
while (true) {
synchronized(consumer) {
try {
consumer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}catch (IOException | TimeoutException e) {
e.printStackTrace();
}finally {
if (connection != null) {
try {
connection.close();
} catch (IOException ignore) {
}
}
}
}
}
另附官方示例地址: 远程过程调用(RPC)(使用Java客户端)