RabbitMQ之远程过程调用(RPC)【译】
在第二个教程中,我们学习了如何使用工作队列在多个worker之间分配耗时的任务。
但是如果我们需要在远程计算机上运行功能并等待结果呢?嗯,这是另外一件事情,这种模式通常被称为远程过程调用(RPC)。
在本教程中我们将使用RabbitMQ的建立一个RPC系统:一个客户端和一个可伸缩的RPC服务器。由于我们没有什么耗时的任务,我们要创建一个返回斐波那契数虚设RPC服务。
客户端接口(Client interface)
为了说明RPC如何使用,我们将创建一个简单的客户端类。它将创建一个名为call的方法——发送RPC请求,并且处于阻塞状态,直到收到应答。
FibonacciRpcClient fibonacciRpc = new FibonacciRpcClient();
String result = fibonacciRpc.call("4");
System.out.println( "fib(4) is " + result);
PRC笔记
尽管PRC是一个常见的模式,它经常受到批评。当程序员不知道他所调用的方法是本地的还是一个缓慢的RPC,问题就出现了。这样的混乱在系统中造成不可预料的结果,并增加了不必要的调试的复杂性,相比于简单的软件,PRC的滥用可能导致造成不可维护的面条式的代码。
考虑到这一点,请参考以下建议:确保能明确分辨出哪些函数是本地的,哪些是远程的。
建立文档,让组件之间的依赖关系更清楚。
处理错误的case,如果RPC服务器挂了很长时间,客户端应该怎么处理?
如果对以上有疑问,请避免使用。如果没有,你也应该使用异步管道,而不是阻塞式的RPC调用,结果被异步地推到下一个计算阶段。
回调队列(Callback queue)
一般来说利用RabbitMQ来做RPC是很简单的。客户端发送请求消息,服务端回复应答消息。为了能收到回复,我们需要发送一个“callback”队列地址在请求里面。我们可以使用默认队列(这是Java客户端独有的):
callbackQueueName = channel.queueDeclare().getQueue();
BasicProperties props = new BasicProperties
.Builder()
.replyTo(callbackQueueName)
.build();
channel.basicPublish("", "rpc_queue", props, message.getBytes());
// ... then code to read a response message from the callback_queue ...
消息属性
AMQP协议预定义了14个属性去发送消息。大部分的属性都很少使用,但是下列除外:
deliveryMode:标记的消息为持久(值为2)或暂时的(任何其他值)。你可能还记得第二个教程中的此属性。
contentType:用于描述MIME类型的编码。例如,对于经常使用JSON编码,是一个很好的做法,将此属性设置为:application/json。
eplyTo: 常用于命名一个回调队列。
correlationId: 用于关联的RPC响应。
我们需要import:
import com.rabbitmq.client.AMQP.BasicProperties;
关联标识(Correlation Id)
在上面介绍的方法中,我们建议为每一个RPC请求建立一个回调队列。这是相当低效的,幸好有一个更好的办法 - 让我们创建每个客户端一个回调队列。
这样产生了一个新的问题,在收到该回调队列的响应的时候,我们并不知道该响应是哪个请求的响应,这就是correlationId属性的用处,我们将它设置为每个请求的唯一值。这样,当我们在回调队列收到一条消息的时候,我们将看看这个属性,就能找到与这个响应相对应的请求。如果我们看到一个未知的correlationId,我们完全可以丢弃消息,因为他不并不属于我们系统。
你也许会问,为什么我们选择丢弃这个消息,而不是抛出一个错误。这是为了解决服务器端有可能发生的竞争情况。尽管可能性不大,但RPC服务器还是有可能在已将应答发送给我们但还未将确认消息发送给请求的情况下死掉。如果这种情况发生,RPC在重启后会重新处理请求。这就是为什么我们必须在客户端优雅的处理重复响应,同时RPC也需要尽可能保持幂等性。
总结
我们的RPC这样工作:
1. 当客户端启动的时候,创建一个匿名的独享的回调队列。
2. 在RPC请求中,客户端发送带有两个属性的消息:一个是设置回调队列的 reply_to 属性,另一个是设置唯一值的 correlation_id 属性。
3. 该请求被发送到rpc_queue队列。
4. RPC工作者(又名:服务器)等待请求发送到这个队列中来。当请求出现的时候,它执行他的工作并且将带有执行结果的消息发送给reply_to字段指定的队列。
5. 客户端等待回调队列里的数据。当有消息出现的时候,它会检查correlation_id属性。如果此属性的值与请求匹配,将它返回给应用。
代码整合
斐波那契数列任务:
private static int fib(int n) throws Exception {
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n-1) + fib(n-2);
}
我们定义一个斐波那契的方法,假定只有有效的正整数输入。(不要指望它为大数据工作,这可能是最慢的递归实现)
我们的RPC服务器RPCServer.java的代码如下:
private static final String RPC_QUEUE_NAME = "rpc_queue";
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
channel.basicQos(1);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
System.out.println(" [x] Awaiting RPC requests");
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
BasicProperties props = delivery.getProperties();
BasicProperties replyProps = new BasicProperties
.Builder()
.correlationId(props.getCorrelationId())
.build();
String message = new String(delivery.getBody());
int n = Integer.parseInt(message);
System.out.println(" [.] fib(" + message + ")");
String response = "" + fib(n);
channel.basicPublish( "", props.getReplyTo(), replyProps, response.getBytes());
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
以上服务端的代码很简单:
- 和通常一样,我们从建立一个连接,一个通道和定义一个队列开始。
- 我们可能需要运行多个服务器进程。为了在多个服务器上均匀分布的负荷,我们需要设置channel.basicQos中的prefetchCount。
- 我们使用basicConsume访问队列。然后,进入while循环中,等待请求消息,完成工作并发送回响应。
RPCClient.java:
private Connection connection;
private Channel channel;
private String requestQueueName = "rpc_queue";
private String replyQueueName;
private QueueingConsumer consumer;
public RPCClient() throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
connection = factory.newConnection();
channel = connection.createChannel();
replyQueueName = channel.queueDeclare().getQueue();
consumer = new QueueingConsumer(channel);
channel.basicConsume(replyQueueName, true, consumer);
}
public String call(String message) throws Exception {
String response = null;
String corrId = java.util.UUID.randomUUID().toString();
BasicProperties props = new BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
channel.basicPublish("", requestQueueName, props, message.getBytes());
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
if (delivery.getProperties().getCorrelationId().equals(corrId)) {
response = new String(delivery.getBody());
break;
}
}
return response;
}
public void close() throws Exception {
connection.close();
}
客户端的代码稍微复杂:
- 我们建立一个连接,一个通道和一个用于接收回复的回调队列。
- 我们订阅“回调”的队列,这样我们就可以接收RPC响应。
- 我们的call方法发出实际的RPC请求。
- 在这里,我们首先生成一个唯一的correlationID,并保存它 - while循环会使用这个值来捕捉适当的响应。
- 接下来,我们发布请求消息时,具有两个属性:的replyTo和的correlationID。
- 在这一点上,我们可以坐下来,等到适当的响应到达。
- while循环正在做一个很简单的工作,对于每一个响应消息它会检查的correlationID是我们要找的人。如果是,它将保存的响应。
- 最后,我们返回响应给用户。
客户端请求:
RPCClient fibonacciRpc = new RPCClient();
System.out.println(" [x] Requesting fib(30)");
String response = fibonacciRpc.call("30");
System.out.println(" [.] Got '" + response + "'");
fibonacciRpc.close();
以上的设计不是唯一可能的实现一个RPC服务的,但它有一些重要的优点:
- 如果RPC服务器速度太慢,则只需运行多个即可。尝试在新的控制台运行的第二RPCServer。
- 在客户端,RPC请求只发送或接收一条消息。不需要像 queue_declare 这样的异步调用。所以RPC客户端的单个请求只需要一个网络往返。
我们的代码依旧非常简单,而且没有试图去解决一些复杂(但是重要)的问题,如:
- 当没有服务器运行时,客户端如何作出反映。
- 客户端是否需要实现类似RPC超时的东西。
- 如果服务器发生故障,并且抛出异常,应该被转发到客户端吗?
- 在处理前,防止混入无效的信息(例如检查边界)
原文地址:https://www.rabbitmq.com/tutorials/tutorial-six-java.html
代码地址:https://github.com/aheizi/hi-mq
相关:
1.RabbitMQ之HelloWorld
2.RabbitMQ之任务队列
3.RabbitMQ之发布订阅
4.RabbitMQ之路由(Routing)
5.RabbitMQ之主题(Topic)
6.RabbitMQ之远程过程调用(RPC)
RabbitMQ之远程过程调用(RPC)【译】的更多相关文章
- RabbitMQ之发布订阅【译】
在上一节中我们创建了一个工作队列,最好的情况是工作队列能够把任务恰到好处的分配给每一个worker.这一节中我们将做一些完全不同的事情--将消息传递给每一个消费者,这种模式被称为发布/订阅. 为了说明 ...
- (转) RabbitMQ学习之远程过程调用(RPC)(java)
http://blog.csdn.net/zhu_tianwei/article/details/40887885 在一般使用RabbitMQ做RPC很容易.客户端发送一个请求消息然后服务器回复一个响 ...
- 传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确。此 RPC 请求中提供了过多的参数。最多应为 2100
出现这个问题的背景是,判断一批激活码在系统中是否已经存在,很傻的一个作法是,把这一批激活码,以in(in (‘ddd‘,‘aaa‘))的形式来处理,导致问题的出现. 后来,查找资料,http://bb ...
- 转:传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确 .
近期在做淘宝客的项目,大家都知道,淘宝的商品详细描述字符长度很大,所以就导致了今天出现了一个问题 VS的报错是这样子的 ” 传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确“ 还说某 ...
- SQLServer 2000 Driver for JDBC][SQLServer]传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确解决方法
问题:[SQLServer 2000 Driver for JDBC][SQLServer]传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确.参数 1 (""): ...
- SQL :“传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确” 错误
其中在DAL层调用存储过程来插入数据的参数 SqlParameter[] parameters = { new S ...
- 遭遇:“传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确” 错误
http://www.cnblogs.com/delphinet/archive/2010/03/09/1681777.html 正在写一个类似文章的发表系统.其中记录文章内容的字段Contents设 ...
- java 执行sql错误 传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确。参数 1 (""): 数据类型 0x38 未知
连接数据库时设置:Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE ,ResultSet.CONCUR_R ...
- 今天遇到的传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确的解决方案
传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确.参数 3 ("@UserName"): 数据类型 0xE7 的数据长度或元数据长度无效. 今天在做数据同步的时候遇 ...
随机推荐
- 使用BeyondCompare比较文件夹下的文件时,相同的文件内容,但显示为不相同
主要原因是: 两个文件行尾标题不一致而导致的,一个是PC,一个是Unix 解决办法: 随便比较文件夹中的两个文件,点击规则,去掉比较行尾(pc/mac/unix)选项,点击确认,回到文件夹比较界面,刷 ...
- windows8中visual studio 2010 编译boost1.57库
参考: http://blog.csdn.net/a06062125/article/details/7773976 http://www.cppfans.org/1317.html http://w ...
- Lithium: HTML5 响应式的单页面模板
在线演示:http://www.gbtags.com/gb/demoviewer/2507/837ac02e-4963-46c9-83ee-a0a0bb867f7f/3.-Lithium|app|in ...
- QtGui.QFileDialog
The QtGui.QFileDialog is a dialog that allows users to select files or directories. The files can be ...
- jQuery如何获得select选中的值?input单选radio选中的值
jQuery取得select选中的值 本来以为jQuery("#select1").val();是取得选中的值, 那么jQuery("#select1").te ...
- JavaScript操作符
一元操作符 只能操作一个值的操作符叫做一元操作符. 递增和递减操作符 递增和递减操作符遵循下列规则: 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减 1 的操作 ...
- 查看正在执行的sql语句
;WITH t AS( SELECT [Spid] = session_Id, ecid, [Database] = DB_NAME(sp.dbid), [User] = nt_username, [ ...
- iOS-开发者相关的几种证书(转)
分享一篇关于苹果开发者证书非常详细的解释. 哈哈,我太懒了,直接转发链接. iOS-开发者相关的几种证书
- java调试打断点和不打断点执行结果不一致问题解决
java程序在调试的时候需要debug来跟踪一下结果,有一种情况是这样的,正常执行java程序就会出现问题,而断点debug跟踪此方法的时候却是正常的,不断测试结果都是这样,由此判断有可能是因为此方法 ...
- springmvc mybatis 声明式事务管理回滚失效,(checked回滚)捕捉异常,传输错误信息
一.知识点及问题 后端框架: Spring .Spring mvc .mybatis 业务需求: client先从服务端获取用户大量信息到client,编辑完毕之后统一Post至服务端,对于数据的改动 ...