RabbitMQ入门(6)——远程过程调用(RPC)
在RabbitMQ入门(2)——工作队列中,我们学习了如何使用工作队列处理在多个工作者之间分配耗时任务。如果我们需要运行远程主机上的某个方法并等待结果怎么办呢?这种模式就是常说的远程过程调用(Remote Procedure Call),简称RPC。
RPC
尽管RPC在计算机中是一种常见的模式,却经常饱受诟病。当程序员不知道方法的调用是本地的还是速度慢的RPC时,可能导致系统不可控、代码难以调试。因此,滥用RPC可能导致代码无法维护。使用RPC的建议:
- 明确方法的调用时本地的还是远程的。
- 提供文档记录,让组件之间的依赖关系清楚明了。
- 记录错误情况,当RPC服务器长时间宕机时,客户端如何处理
当有疑问是,要避免使用RPC,如果可以,尽量使用异步管道结果被异步推到下一个阶段,而不是像RPC那样阻塞。
这一篇我们将使用RabbitMQ建立一个PRC系统:一个客户端和一个可伸缩的PRC服务器。我们将创建一个返回Fibonacci数的虚拟服务。
客户端接口
为了说明RPC如何使用,首先创建一个客户端的类,它暴露一个名为call的方法发送RPC请求,直到收到应答。
FibonacciRpcClient fibonacciRpc = new FibonacciRpcClient();
String result = fibonacciRpc.call("4");
System.out.println( "fib(4) is " + result);
回调队列(Callback queue)
一般来说,在RabbitMQ上执行RPC是比较简单的。客户端发送请求消息,服务器响应消息。为了接收响应,我们需要发送一个带有“回调”队列地址的请求。可以使用默认的队列,它在java客户端具有唯一性。
callbackQueueName = channel.queueDeclare().getQueue();
BasicProperties props = new BasicProperties
.Builder()
.replyTo(callbackQueueName)
.build();
channel.basicPublish("", "rpc_queue", props, message.getBytes());
消息属性(Message properties)
AMQP 0-9-1协议中预先定义了一组14个带有消息的属性。以下几个是比较常用的:
- deliveryMode :将消息标记为持久的(值为2)或瞬态(任何其他值)。
- contentType : 用于描述编码的mime类型。例如,对于经常使用的JSON编码,将该属性设置为:application/JSON是一种很好的做法。
- replyTo : 通常用来命名一个回调队列
- correlationId : 有助于将RPC响应与请求关联起来
关联ID(Correlation Id)
上面的方法中为每一个RPC请求创建一个回调队列的方式效率很低。幸好我们可以为每个客户端创建一个回调队列。然而这又引出了一个新的问题:回调队列中收到的响应我们并不知道是哪个请求发出的。这就是correlationId 属性的作用,我们将为每个请求设置一个唯一的correlationId 。当我们从回调队列收到一条消息时,首先查看correlationId 属性,这样我们就能将请求和响应对应起来了。如果发现一个未知的correlationId,说明它与我们的请求无关,就可以安全地丢弃这条消息了。
也许你会问为什么忽略回调队列的未知消息而不是处理错误?这可能是服务端的竞态条件导致的。RPC服务器可能在发送响应之后,在发送请求的确认消息之前宕机。这就是为什么我们在客户端必须优雅地处理重复的响应,而RPC在理想情况下应该是幂等的。
总结

RPC工作流程:
- 当客户端启动时,它将创建一个匿名的唯一回调队列
- 对于RPC请求,客户端会发送一个包含两个属性的消息,
replyTo属性设置回调队列,correlationId为每一个请求设置唯一的值 - 请求被发送到
rpc_queue队列 - RPC服务器正在等待该队列上的请求,当请求到达时,服务器执行任务并使用replyTo字段的队列将结果返回给客户端。
- 客户端等待回调队列。当消息到达时,首先检查
correlationId属性值,如果它和请求的值匹配,它将返回对应用程序的响应
代码清单
RPCServer:
package com.xxyh.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RPCServer {
private static final String RPC_QUEUE_NAME = "rpc_queue";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = null;
try {
connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
channel.basicQos(1);
System.out.println("等待RPC请求......");
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 = "";
try {
String message = new String(body,"UTF-8");
int n = Integer.parseInt(message);
System.out.println(" 计算fib(" + message + ")");
response += fib(n);
}
catch (RuntimeException e){
System.out.println(" [.] " + e.toString());
}
finally {
channel.basicPublish( "", properties.getReplyTo(), replyProps, response.getBytes("UTF-8"));
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static int fib(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
}
RPCClient:
package com.xxyh.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
public class RPCClient {
private static final String RPC_QUEUE_NAME = "rpc_queue";
private Connection connection;
private Channel channel;
private String replyQueueName;
public RPCClient() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
connection = factory.newConnection();
channel = connection.createChannel();
replyQueueName = channel.queueDeclare().getQueue();
}
public String call(String message) throws IOException, InterruptedException {
String corrId = UUID.randomUUID().toString();
AMQP.BasicProperties props = new AMQP.BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
channel.basicPublish("", RPC_QUEUE_NAME, props, message.getBytes("utf-8"));
final BlockingQueue<String> response = new ArrayBlockingQueue<>(1);
channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
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) {
RPCClient fibonacciRpc = null;
String response = null;
try {
fibonacciRpc = new RPCClient();
System.out.println(Thread.currentThread().getName() + " 请求 fib(30)");
response = fibonacciRpc.call("30");
System.out.println(Thread.currentThread().getName() + " 获得结果: " + response);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (fibonacciRpc != null) {
try {
fibonacciRpc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
RabbitMQ入门(6)——远程过程调用(RPC)的更多相关文章
- RabbitMQ入门:远程过程调用(RPC)
假如我们想要调用远程的一个方法或函数并等待执行结果,也就是我们通常说的远程过程调用(Remote Procedure Call).怎么办? 今天我们就用RabbitMQ来实现一个简单的RPC系统:客户 ...
- (转) RabbitMQ学习之远程过程调用(RPC)(java)
http://blog.csdn.net/zhu_tianwei/article/details/40887885 在一般使用RabbitMQ做RPC很容易.客户端发送一个请求消息然后服务器回复一个响 ...
- RabbitMQ入门:总结
随着上一篇博文的发布,RabbitMQ的基础内容我也学习完了,RabbitMQ入门系列的博客跟着收官了,以后有机会的话再写一些在实战中的应用分享,多谢大家一直以来的支持和认可. RabbitMQ入门系 ...
- RabbitMQ入门教程(八):远程过程调用RPC
原文:RabbitMQ入门教程(八):远程过程调用RPC 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.cs ...
- [译]RabbitMQ教程C#版 - 远程过程调用(RPC)
先决条件 本教程假定 RabbitMQ 已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需要调整连接设置. 从哪里获得帮助 如果您在阅读本教程时遇到困难, ...
- .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇
.NET 环境中使用RabbitMQ 在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...
- 转载RabbitMQ入门(6)--远程调用
远程过程调用(RPC) (使用Java客户端) 在指南的第二部分,我们学习了如何使用工作队列将耗时的任务分布到多个工作者中. 但是假如我们需要调用远端计算机的函数,等待结果呢?好吧,这又是另一个故事了 ...
- RabbitMQ入门-理论
目录 RabbitMQ简介 RabbitMQ原理简介 RabbitMQ安装 .NET Core 使用 RabbitMQ Hello World 工作队列 扇型交换机 直连交换机 主题交换机 远程过程调 ...
- 消息中间件 RabbitMQ 入门篇
消息中间件 RabbitMQ 入门篇 五月君 K8S中文社区 今天 作者:五月君,来源:Nodejs技术栈 从不浪费时间的人,没有工夫抱怨时间不够.—— 杰弗逊 RabbitMQ 是一套开源(MP ...
随机推荐
- 转!!mybatis xml 传值 if test判断
当mapper方法传参数 为 String时,且xml中药进行参数比较 比如 是不是等于1 或者等于2 方式1. 方式2. 转自:https://blog.csdn.net/chenaini119/a ...
- golang: multiple http.writeHeader calls
背景: golang的http服务,读取文件,提供给client下载时候. 出现 multiple http.writeHeader calls 错误. func DownloadFile(w htt ...
- Design Patterns Example Code (in C++)
Overview Design patterns are ways to reuse design solutions that other software developers have crea ...
- 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
本文背景: 在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用:根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制. 本 ...
- 十個必用的 Vim Plugin
◎ The NERD Tree 操作 Vim 時,通常都在 Terminal 底下作用,無法像一般的 GUI 應用程式可以以樹狀目錄來瀏覽檔案. The NERD Tree 是一將檔案目錄 ...
- 入参是小数的String,返回小数乘以100的String
String money = request.getParameter("orderAmt"); BigDecimal moneyDecimal = new BigDecimal( ...
- centos7 设置mongodb远程连接
/etc/mongod.conf 更改 bindIp: 127.0.0.1 为 bindIp: 0.0.0.0
- C++入门(1)
#include<>直接从编译器自带的函数库中寻找文件 #include""是先从自定义的文件中找 ,如果找不到在从函数库中寻找文件 采用"< > ...
- 文字识别的google的库 tesseract
https://github.com/tesseract-ocr/tesseract https://github.com/tesseract-ocr/tessdata 字体识 ...
- HI35xx平台调试笔记
1.音视频数据循环采集 a. 在sample_venc.c文件中,海思官方是把采集到的数据都保存到文件中,我们需要更改到缓存里,以便后面推送到rtsp/rtmp/hls服务端. ; i < s3 ...