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. 转!!mybatis xml 传值 if test判断

    当mapper方法传参数 为 String时,且xml中药进行参数比较 比如 是不是等于1 或者等于2 方式1. 方式2. 转自:https://blog.csdn.net/chenaini119/a ...

  2. golang: multiple http.writeHeader calls

    背景: golang的http服务,读取文件,提供给client下载时候. 出现 multiple http.writeHeader calls 错误. func DownloadFile(w htt ...

  3. Design Patterns Example Code (in C++)

    Overview Design patterns are ways to reuse design solutions that other software developers have crea ...

  4. 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件

    本文背景: 在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用:根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制. 本 ...

  5. 十個必用的 Vim Plugin

    ◎ The NERD Tree 操作 Vim 時,通常都在 Terminal 底下作用,無法像一般的 GUI    應用程式可以以樹狀目錄來瀏覽檔案. The NERD Tree    是一將檔案目錄 ...

  6. 入参是小数的String,返回小数乘以100的String

    String money = request.getParameter("orderAmt"); BigDecimal moneyDecimal = new BigDecimal( ...

  7. centos7 设置mongodb远程连接

    /etc/mongod.conf 更改 bindIp: 127.0.0.1 为 bindIp: 0.0.0.0

  8. C++入门(1)

    #include<>直接从编译器自带的函数库中寻找文件 #include""是先从自定义的文件中找 ,如果找不到在从函数库中寻找文件 采用"< > ...

  9. 文字识别的google的库 tesseract

    https://github.com/tesseract-ocr/tesseract https://github.com/tesseract-ocr/tessdata             字体识 ...

  10. HI35xx平台调试笔记

    1.音视频数据循环采集 a. 在sample_venc.c文件中,海思官方是把采集到的数据都保存到文件中,我们需要更改到缓存里,以便后面推送到rtsp/rtmp/hls服务端. ; i < s3 ...