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. Python中的Numpy

    引用Numpy import numpy as np 生成随机数据 # 200支股票 stock_cnt = 200 # 504个交易日 view_days = 504 # 生成服从正态分布:均值期望 ...

  2. 前端开发 - JQuery - 下

    二十五.jquery的事件 <!DOCTYPE html> <html lang="en"> <head> <meta charset=& ...

  3. MFC Spin 控件

    一般应用: 设置属性: Auto Buddy(自动取关联控件为TAB顺序前一个)Set Buddy Interger(使控件设置关联控件数值,这个值可以是十进制或十六进制)Wrap(数值超过范围时循环 ...

  4. Java-多线程基本

    Java-多线程基本 一 相关的概念 进程:是一个正在执行中的程序 每个进程都有一个执行的顺序,该顺序是一个执行路径,或者叫一个控制单元 线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行 ...

  5. 单例Singleton模式的两种实现方法

    在设计模式中,有一种叫Singleton模式的,用它可以实现一次只运行一个实例.就是说在程序运行期间,某个类只能有一个实例在运行.这种模式用途比较广泛,会经常用到,下面是Singleton模式的两种实 ...

  6. php微信支付回调验证

    //字典排序拼接字符串 function getWxPaySignature($arr){ ksort($arr); $str = ''; foreach ($arr as $k=>$a){ $ ...

  7. C的指针疑惑:C和指针6(指针)

    NULL: 对所有指针变量进行显式的初始化是种好事:(1)如果你知道指针将被初始化为什么地址,就直接初始化该地址, (2)否则把它初始化位NULL. 注意:假定变量a存储于位置100. × = 看上去 ...

  8. PHP 基础篇 - PHP 错误级别详解

    一.前言 最近经常看到工作 2 年左右的童鞋写的代码也会出现以静态方法的形式调用非静态方法,这是个 Deprecated 级别的语法错误,代码里不应该出现的.对方很郁闷,说:为什么我的环境可以正常运行 ...

  9. UVA10905: Children's Game(排序)

    题目:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=68990#problem/A 题目需求:,给n个数字,将它们重新排序得到一个最大的数 ...

  10. (转)库函数之 API

    API是库函数,这些库函数操作系统提供开发人员开发应用程序使用的. API函数内部应该是C C++ 或者汇编语言实现的.     如果想在程序里面使用API函数 需要包含头文件  Windows.h. ...