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)的更多相关文章

  1. RabbitMQ入门:远程过程调用(RPC)

    假如我们想要调用远程的一个方法或函数并等待执行结果,也就是我们通常说的远程过程调用(Remote Procedure Call).怎么办? 今天我们就用RabbitMQ来实现一个简单的RPC系统:客户 ...

  2. (转) RabbitMQ学习之远程过程调用(RPC)(java)

    http://blog.csdn.net/zhu_tianwei/article/details/40887885 在一般使用RabbitMQ做RPC很容易.客户端发送一个请求消息然后服务器回复一个响 ...

  3. RabbitMQ入门:总结

    随着上一篇博文的发布,RabbitMQ的基础内容我也学习完了,RabbitMQ入门系列的博客跟着收官了,以后有机会的话再写一些在实战中的应用分享,多谢大家一直以来的支持和认可. RabbitMQ入门系 ...

  4. RabbitMQ入门教程(八):远程过程调用RPC

    原文:RabbitMQ入门教程(八):远程过程调用RPC 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.cs ...

  5. [译]RabbitMQ教程C#版 - 远程过程调用(RPC)

    先决条件 本教程假定 RabbitMQ 已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需要调整连接设置. 从哪里获得帮助 如果您在阅读本教程时遇到困难, ...

  6. .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇

    .NET 环境中使用RabbitMQ   在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...

  7. 转载RabbitMQ入门(6)--远程调用

    远程过程调用(RPC) (使用Java客户端) 在指南的第二部分,我们学习了如何使用工作队列将耗时的任务分布到多个工作者中. 但是假如我们需要调用远端计算机的函数,等待结果呢?好吧,这又是另一个故事了 ...

  8. RabbitMQ入门-理论

    目录 RabbitMQ简介 RabbitMQ原理简介 RabbitMQ安装 .NET Core 使用 RabbitMQ Hello World 工作队列 扇型交换机 直连交换机 主题交换机 远程过程调 ...

  9. 消息中间件 RabbitMQ 入门篇

    消息中间件 RabbitMQ 入门篇 五月君 K8S中文社区 今天   作者:五月君,来源:Nodejs技术栈 从不浪费时间的人,没有工夫抱怨时间不够.—— 杰弗逊 RabbitMQ 是一套开源(MP ...

随机推荐

  1. BBS项目部署之数据库的处理

    一  数据库的处理: 1.1上传bbs.sql(数据库中的数据) 1.2在mysql中创建bbs库,并导入数据库SQL脚本 在mysqld的文件夹中: mysql> create databas ...

  2. (1.3.1)连接安全(连接实例与网络协议及TDS端点)

    连接安全是sql server安全配置的第1道防线,它保证只有许可的客户端能够连接sql server,而且可以限制连接可用的通道(各种网络协议). 1.连接到sql server实例 sql ser ...

  3. awk经常使用字符串处理函数

    gsub(regexp, replacement [, target]) Search target for all of the longest, leftmost, nonoverlapping  ...

  4. 出现 Request Entity Too Large问题的解决方法

    根据经验判断应该是上传文件大小被限制了,检查了应用配置是10M,把它设置成100M,重启服务也不能解决问题. 原来我们的tomcat是通过nginx发现服务代理的,问题就出现nginx服务器上,原来n ...

  5. 小tip: 使用SVG寥寥数行实现圆环loading进度效果

    二.正文 设计师设计了一个图片上传圆环loading进度效果.如下截图: 首先,CSS3是可以实现的,以前写过一篇转大饼的文章:“CSS3实现鸡蛋饼饼状图loading等待转转转”.原理跟这个一模一样 ...

  6. 理解java动态代理

    java动态代理是java语言的一项高级特性.在平时的项目开发中,可能很难遇到动态代理的案例.但是动态代理在很多框架中起着不可替代的作用,例如Spring的AOP.今天我们就聊一聊java动态代理的实 ...

  7. centos7提示ifconfig command not found解决

    安装centos7时,选择了minimal install的话,没有安装网络组件. yum install net-tools 可以解决问题.

  8. Hadoop2.7.3+spark2.1.0+hbase0.98分布式集群部署

    运行环境配置(10.79.115.111-114) 1.关闭防火墙与selinux 关闭防火墙: CentOS 7.x默认使用的是firewall作为防火墙. systemctl stop firew ...

  9. SpringMVC的映射器,适配器,控制器

    一.各司其职   (1)HandlerMapping映射器 根据客户端请求的url,找到处理本次请求的处理器,即将请求的路径和controller关联起来 (2)HandlerAdapter适配器 对 ...

  10. 关于cgi、FastCGI、php-fpm、php-cgi(复制)

    首先,CGI是干嘛的?CGI是为了保证web server传递过来的数据是标准格式的,方便CGI程序的编写者. web server(比如说nginx)只是内容的分发者.比如,如果请求/index.h ...