在第三篇文章中, 我们学习了怎么使用队列在多了消息消费者当中进行耗时任务轮询。

但是如果我们想要在远程电脑上运行一个方法,然后等待其执行结果,这就是一个不同的场景,这种就是我们一般讲的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)的更多相关文章

  1. 译:6.RabbitMQ Java Client 之 Remote procedure call (RPC,远程过程调用)

    在  译:2. RabbitMQ 之Work Queues (工作队列)  我们学习了如何使用工作队列在多个工作人员之间分配耗时的任务. 但是如果我们需要在远程计算机上运行一个函数并等待结果呢?嗯,这 ...

  2. Remote procedure call (RPC)

    Remote procedure call (RPC) (using the .NET client) Prerequisites This tutorial assumes RabbitMQ isi ...

  3. 官网英文版学习——RabbitMQ学习笔记(八)Remote procedure call (RPC)

    在第四篇学习笔记中,我们学习了如何使用工作队列在多个工作者之间分配耗时的任务.   但是,如果我们需要在远程计算机上运行一个函数并等待结果呢?这是另一回事.这种模式通常称为远程过程调用或RPC.   ...

  4. RabbitMQ 原文译1.2--"Hello Word"

    本系列文章均来自官网原文,属于个人翻译,如有雷同,权当个人归档,忽喷. .NET/C# RabbitMQ 客户端下载地址:https://github.com/rabbitmq/rabbitmq-do ...

  5. RabbitMQ 原文译1.1--HelloWord

    本系列文章均来自官网原文,属于个人翻译,如有雷同,权当个人归档,忽喷. RabitMQ 是一个消息中间件,其实就是从消息生产者那里接受消息,然后发送给消息消费者.在这个传输过程中,可以定义一些缓存,持 ...

  6. RabbitMQ 原文译03--发布和订阅

    发布/订阅 在之前的案例中我们创建了一个工作队列,这个工作队列的实现思想就是一个把每一个任务平均分配给每一个执行者,在这个篇文章我们会做一些不一样的东西,把一个消息发送给多个消费者,这种模式就被称作& ...

  7. RabbitMQ 原文译02--工作队列

    工作队列: 在上一篇文章中我们我们创建程序发送和接受命名队列中的消息,在这篇文章我会创建一个工作队列,用来把耗时的操作分配给多个执行者. 工作队列(任务队列)的主要实现思想是避免马上执行资源密集型的任 ...

  8. RabbitMQ 原文译05--Topics

    在之前的系统中,我们改进了我们的日志系统,我们使用direct 交换机代替fanout交换机,可以实现选择性的接受日志. 虽然使用direct 交换机改进了我们的系统,但是对于多种条件的判断,依然存在 ...

  9. RabbitMQ 原文译04--路由

    在前一篇文章中我们构建了一个简单的日志系统,我们可以向多个接受者广播消息. 在这篇文章我,我们将要添加一些功能使得针对部分消息的接受成为可能,例如我们只对错误的消息进行磁盘记录,同时又可以把所有的消息 ...

随机推荐

  1. HDU 2682

    思路:由于题目对能相连的点有限制,必须将这些点处理,能相连的点合并到一个集合中,最后查看是否所有点都在一个集合里,若都在说明是一个连通图,存在最小生成树,否则图不连通,不存在最小花费. #includ ...

  2. POJ1050:To the max

    poj1050:http://poj.org/problem?id=1050 * maximum-subarray 问题的升级版本~ 本题同样是采用DP思想来做,同时有个小技巧处理:就是把二维数组看做 ...

  3. MVC client validation after PartialView loaded via Ajax MVC3中 弹出 Dialog时候 提交的时候 使用 Jquery 不验证 form表单 的解决办法

    I came across this scenario whereby my main View uses Ajax posts to retrieve PartialViews and delive ...

  4. EGOImageView的使用方法及注意事项

    1.下载EGOImageView及其相关的类库 EGOImageLoading 将EGOCache.EGOImageButton.EGOImageView.EGOImageLoader全部添加到工程下 ...

  5. 安卓问题http://blog.csdn.net/xb12369/article/details/50510302

  6. vmwear 及docker

    1.安装vmwear,开启cpu虚拟化 2.http://jingyan.baidu.com/article/eae0782787b4c01fec548535.html 3.docker

  7. robotframework-FQA

    发现是一波三折,刚开始信步漫游,就又遇上了沟,整理一下吧:  1.WebDriverException: Message: 'geckodriver' executable needs to be i ...

  8. window nginx 启动无提示错误,却没有listen 80port

    一直使用虚拟机来使用web+hostonly方式; 今天为了測试一个php平台的window系统兼容性, 在官方下载了window-nginx 1.9.1版本号; 解压到文件夹, 执行nginx.ex ...

  9. codeforces 132C Logo Turtle--- dp dfs

    题目在这里:点击打开链接 题意: F表示前进一步,T表示变成反方向 给一串FT字符,和一个n,表示可以改变多少次,求可以走到的离原点最远的距离 改变就是F变成T.T变成F 关键: dfs(int d, ...

  10. 雄踞AppStore榜首的游戏&lt;别踩到白块儿&gt;源码分析和下载(一)

    AppStore和Android市场情况 莫名其妙爆红的游戏 真的莫名其妙,笔者下这个游戏两次.第一次在豌豆荚排行榜看到这款游戏,名字怪怪的,下载下来尝试一下,没认为有什么新颖的,还在思虑这是不是刷榜 ...