RabbitMQ 原文译06--Remote procedure call(RPC)
在第三篇文章中, 我们学习了怎么使用队列在多了消息消费者当中进行耗时任务轮询。
但是如果我们想要在远程电脑上运行一个方法,然后等待其执行结果,这就是一个不同的场景,这种就是我们一般讲的RPC(远程过程调用)。
在这篇文章当中我们将会使用RabbitMQ构建一个简单的RPC系统,一个客户端和一个服务端,由于我们没有耗时任务需要轮询,因此我们创建一个假的返回斐波那契数字的服务端。
客户端接口
为了说明RPC服务是运行方式,我们创建一个了简单的Client类,该类有一个Call的方法用来发送RPC请求道服务端然后阻塞直到请求结果的返回。
var rpcClient = new RPCClient();
Console.WriteLine(" [x] Requesting fib(30)");
var response = rpcClient.Call("");
Console.WriteLine(" [.] Got '{0}'", response);
rpcClient.Close();
注意:虽然RPC在计算处理中是一种非常常见的模型,但是经常有非常多的争议,当编程人员不注意一个方法函数是本地的还是较慢的RPC呼叫就会出现问题,例如当一个不可预测的系统在返回结果的调试信息当中添加一些非必要的复杂信息时会使我们感到迷惑。
代替简单的软件,错误的使用RPC导致代码变得不可维护。
请记住一下建议:
>确保清楚哦知道那个函数是本地的那个函数是RPC函数。
>文档化你的系统,确保组件之间的依赖更加清晰。
>处理异常场景,当远程RPC服务长时间中断时,客户端应该怎么处理。
当存在疑问时避免使用RPC,而应该使用异步管道--代替RPC的功能--如阻塞,可以将结果带入到下一个计算阶段。
回调队列
通常情况下,在不使用RabbitMQ进行RPC调用时非常简单的,客户端发起请求等待服务处返回结果。在RabbitMQ当中为了能够接受处理结果,我们需要把回调队列的地址随着请求一起发送给服务器。
var corrId = Guid.NewGuid().ToString();
var props = channel.CreateBasicProperties();
props.ReplyTo = replyQueueName;
props.CorrelationId = corrId; var messageBytes = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "", routingKey: "rpc_queue", basicProperties: props, body: messageBytes);
Message properties
AMQP预定义了14个属性可以随消息一起发送,其中大多数的属性很少使用,除了下面几个
DeliveryMode:标记消息是否是持久化(值为2)或者是非持久化(值为其他:1)
contentType:用来描述编码的的mime-type,例如对于经常使用的json,把其值设为application/json是一个非常不错的案例。
replyTo:通常被用来作为回调的队列名。
correlationId:匹配RPC请求消息和响应消息之间的关联是非常有用的。
Correlation Id
在之前呈现的方法中无门为每一个RPC请求都创建了一个回调队列--这是非常低效的,幸运的是有一个更好的方式--我们为每一个客户端创建一个回调队列。
这样就带来了一个新的问题,当接收到一个结果的时候我们不知道改结果对应于哪个RPC请求,这就是为什么使用correlationId 属性。对于每一个RPC请求来说,correlationId 都是全局唯一的,之后,当我们从回调队列当中接收到消息的时候我们就可以根据干属性值跟PRC请求匹配起来。如果我们发现了一个未知的correlationId值,我们可以放心的把它丢弃--它不属于我们的系统。
你可能会问问什么要丢弃一个未知的correlationId,而不是产生一个失败错误?这是由于可能的竞争机制,虽然这种情况是非常少见的,但是存在这种可能RPC服务刚刚把计算结果放入回调队列就挂了,这时还没有来的及进行对Request进行Ack确认,这种情况下当RPC服务器冲新启动的时候就会把该条消息重新再处理一次,这就是为什么客户端需要平滑的处理重复的correlationId结果,RPC服务在理想情况下是幂等的。
总结

我们的RPC系统将会这样工作:
>当客户端启动的时候它会创建一个匿名的排他队列
>对于RPC请求,客户端在发送消息上设置两个属性,replyTo--用作回调队列,correlationId--每一个请求都是唯一值。
>请求被发送到rpc_queue 队列
>RPC服务端等待rpc_queue 上面的请求,当请求出现时,它处理请求然后把结果发送到replyTo标示的回调队列上去。
>客户端在回调队列上等待结果,当消息出现时,它匹配消息的correlationId值,如果匹配成功,就会将该消息返回给应用程序。
整合如下:
斐波那契任务:
private static int fib(int n)
{
if (n == || n == ) return n;
return fib(n - ) + fib(n - );
}
我们声明一个斐波那契函数(假定输入是一个可用的正数),不要期望这时一个非常大的数字,虽然这个这个可能是最慢的递归算法。
RPCServer.cs
using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text; class RPCServer
{
public static void Main()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection())
using(var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "rpc_queue",
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
channel.BasicQos(, , false);
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(queue: "rpc_queue",
noAck: false,
consumer: consumer);
Console.WriteLine(" [x] Awaiting RPC requests"); while(true)
{
string response = null;
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue(); var body = ea.Body;
var props = ea.BasicProperties;
var replyProps = channel.CreateBasicProperties();
replyProps.CorrelationId = props.CorrelationId; try
{
var message = Encoding.UTF8.GetString(body);
int n = int.Parse(message);
Console.WriteLine(" [.] fib({0})", message);
response = fib(n).ToString();
}
catch(Exception e)
{
Console.WriteLine(" [.] " + e.Message);
response = "";
}
finally
{
var responseBytes = Encoding.UTF8.GetBytes(response);
channel.BasicPublish(exchange: "",
routingKey: props.ReplyTo,
basicProperties: replyProps,
body: responseBytes);
channel.BasicAck(deliveryTag: ea.DeliveryTag,
multiple: false);
}
}
}
} /// <summary>
/// Assumes only valid positive integer input.
/// Don't expect this one to work for big numbers,
/// and it's probably the slowest recursive implementation possible.
/// </summary>
private static int fib(int n)
{
if(n == || n == )
{
return n;
} return fib(n - ) + fib(n - );
}
}
服务端的代买非常的直接
>开始非常普通,我们创建连接,会话,声明一个队列
>我们可能希望有多个服务器来处理请求,为了在多个服务器之间实现负载我们需要在设置channel.basicQos的prefetchCount
>我们使用basicConsume 来接收队列消息,然后今日一个while循环来接收消息,处理消息,发送响应信息。
PRCClient.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; class RPCClient
{
private IConnection connection;
private IModel channel;
private string replyQueueName;
private QueueingBasicConsumer consumer; public RPCClient()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
connection = factory.CreateConnection();
channel = connection.CreateModel();
replyQueueName = channel.QueueDeclare().QueueName;
consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(queue: replyQueueName,
noAck: true,
consumer: consumer);
} public string Call(string message)
{
var corrId = Guid.NewGuid().ToString();
var props = channel.CreateBasicProperties();
props.ReplyTo = replyQueueName;
props.CorrelationId = corrId; var messageBytes = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "",
routingKey: "rpc_queue",
basicProperties: props,
body: messageBytes); while(true)
{
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
if(ea.BasicProperties.CorrelationId == corrId)
{
return Encoding.UTF8.GetString(ea.Body);
}
}
} public void Close()
{
connection.Close();
}
} class RPC
{
public static void Main()
{
var rpcClient = new RPCClient(); Console.WriteLine(" [x] Requesting fib(30)");
var response = rpcClient.Call("");
Console.WriteLine(" [.] Got '{0}'", response); rpcClient.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("");
System.out.println(" [.] Got '" + response + "'");
fibonacciRpc.close();
RabbitMQ 原文译06--Remote procedure call(RPC)的更多相关文章
- 译:6.RabbitMQ Java Client 之 Remote procedure call (RPC,远程过程调用)
在 译:2. RabbitMQ 之Work Queues (工作队列) 我们学习了如何使用工作队列在多个工作人员之间分配耗时的任务. 但是如果我们需要在远程计算机上运行一个函数并等待结果呢?嗯,这 ...
- Remote procedure call (RPC)
Remote procedure call (RPC) (using the .NET client) Prerequisites This tutorial assumes RabbitMQ isi ...
- 官网英文版学习——RabbitMQ学习笔记(八)Remote procedure call (RPC)
在第四篇学习笔记中,我们学习了如何使用工作队列在多个工作者之间分配耗时的任务. 但是,如果我们需要在远程计算机上运行一个函数并等待结果呢?这是另一回事.这种模式通常称为远程过程调用或RPC. ...
- RabbitMQ 原文译1.2--"Hello Word"
本系列文章均来自官网原文,属于个人翻译,如有雷同,权当个人归档,忽喷. .NET/C# RabbitMQ 客户端下载地址:https://github.com/rabbitmq/rabbitmq-do ...
- RabbitMQ 原文译1.1--HelloWord
本系列文章均来自官网原文,属于个人翻译,如有雷同,权当个人归档,忽喷. RabitMQ 是一个消息中间件,其实就是从消息生产者那里接受消息,然后发送给消息消费者.在这个传输过程中,可以定义一些缓存,持 ...
- RabbitMQ 原文译03--发布和订阅
发布/订阅 在之前的案例中我们创建了一个工作队列,这个工作队列的实现思想就是一个把每一个任务平均分配给每一个执行者,在这个篇文章我们会做一些不一样的东西,把一个消息发送给多个消费者,这种模式就被称作& ...
- RabbitMQ 原文译02--工作队列
工作队列: 在上一篇文章中我们我们创建程序发送和接受命名队列中的消息,在这篇文章我会创建一个工作队列,用来把耗时的操作分配给多个执行者. 工作队列(任务队列)的主要实现思想是避免马上执行资源密集型的任 ...
- RabbitMQ 原文译05--Topics
在之前的系统中,我们改进了我们的日志系统,我们使用direct 交换机代替fanout交换机,可以实现选择性的接受日志. 虽然使用direct 交换机改进了我们的系统,但是对于多种条件的判断,依然存在 ...
- RabbitMQ 原文译04--路由
在前一篇文章中我们构建了一个简单的日志系统,我们可以向多个接受者广播消息. 在这篇文章我,我们将要添加一些功能使得针对部分消息的接受成为可能,例如我们只对错误的消息进行磁盘记录,同时又可以把所有的消息 ...
随机推荐
- 【转】Xcode常用快捷键与技巧分享
原文网址:http://www.jianshu.com/p/039954b0cbe0 工欲善其事必先利其器. 虽然Xcode编写objective-c or swift很完美, 但了解其工具的常用快捷 ...
- 《深度探索C++对象模型》学习笔记
1.转型其实是一种编译器指令, 大部分情况下它并不改变一个指针所含的真正地址,它只影响"被指出之内存的大小和内容"的解释方式. 2.Global objects的内存保证会在程序启 ...
- lightoj 1012
水题,dfs #include<cstdio> #include<string> #include<cstring> #include<iostream> ...
- [Stephen]Export from Excel to ALM
1.根据当前安装的ALM版本和Excel版本到https://hpln.hp.com/page/alm-excel-addin-page中对应的插件进行下载安装,安装时Excel需要关闭.安装成功后, ...
- bzoj 1924 [Sdoi2010]所驼门王的宝藏(构图,SCC,DP)
Description Input 第一行给出三个正整数 N, R, C. 以下 N 行,每行给出一扇传送门的信息,包含三个正整数xi, yi, Ti,表示该传送门设在位于第 xi行第yi列的藏宝宫室 ...
- 活用maven使web.xml可以用maven变量
活用maven使web.xml可以用maven变量 废话不多说,直接上代码 <build> <finalName>${finalWarName}</finalName&g ...
- CGAffineTransformMake(a,b,c,d,tx,ty) 矩阵运算的原理
简记: CGAffineTransformMake(a,b,c,d,tx,ty) ad缩放bc旋转tx,ty位移,基础的2D矩阵 公式 x=ax+cy+tx y=bx+dy+ty 1.矩阵的基本 ...
- chroot 命令实例讲解
我是一个刚接触 Linux 和 Unix 的新手.我该如何改变一个命令的根目录?我要怎样改变一个进程的根目录呢,比如用 chroot 命令将web服务与文件系统隔离?我要如何使用 chroot 恢复密 ...
- Win+R指令(2)
1. gpedit.msc-----组策略 2. sndrec32-------录音机 3. Nslookup-------IP地址侦测器 ,是一个 监测网络中 DNS 服务器是否能正确实现域名解析的 ...
- progressBar 自定义
自定义 ProgressBar 进度条 自定义样式[复制链接] 黑牛 黑牛当前离线 威望 33 在线时间 31 小时 金钱 443 贡献 10 诚信度 0 最后登录 2013-10-17 ...