在第二个教程中,我们了解到如何在多个worker中使用Work Queues分发费时的任务。

但是,如果我们需要在远程运行一个函数并且等待结果该怎么办呢?这个时候,我们需要另外一个模式了。这种模式通常被叫做Remote Procedure Call 或者RPC.

在这个教程中,我们将使用RabbitMQ来建立一个RPC系统:a client和a scalable RPC server.

Client interface

 为了说明RPC服务怎样被使用,我们将创建一个简单的Client class(客户端类)。它会暴露一个发送RPC请求的名叫Call的方法并且会阻塞到接收到answer.

var rpcClient = new RPCClient();

Console.WriteLine(" [x] Requesting fib(30)");var response = rpcClient.Call("");
Console.WriteLine(" [.] Got '{0}'", response); rpcClient.Close();

Callback queue

一般来说,通过RabbitMQ来做RPC是简单的。客户端(client)发送request message并且服务端(server)返回response message. 为了接收到response,我们需要在request上发送一个callback queue address(回调队列地址).

var props = channel.CreateBasicProperties();
props.ReplyTo = replyQueueName; //设置callback queue name
var messageBytes = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "",
routingKey: "rpc_queue",
basicProperties: props,
body: messageBytes);
// ... then code to read a response message from the callback_queue ...
Message properties

AMQP协议在message上预定义了14个属性的集合。 大部分属性很少使用,下面的使用比较多:

  • Persistent : 使message持久化

  • DeliveryMode : 那些熟悉这个协议的可能会使用这个属性而不是Persistent.来做持久化。

  • ContentType : 用来描述编码类型。例如,经常使用的JSON编码,通常设置属性为:application/json

  • ReplyTo : 用来命名callback queue(回调队列)

  • CorrelationId : 用来关联RPC Response 和request

Correlation Id

在之前我们讲的方法中,我们建议为每一个RPC request建立一个callback queue. 那样很没有效率,幸运的,还有一种更好的方法:我们为每个client创建单独的一个callback queue.

这个时候我们需要CorrelationId属性来关联response和request. 每个request都有唯一的correlationId. 当我们在队列中收到一个message,我们看下这个属性,并且根据它我们来匹配response和request. 如果我们看到一个不知道的CorrelationId值,我们会安全的丢掉这个message. 它不属于我们的requests.

你可能会问,为什么我们忽视callback queue中不知道的message(unknow messages),而不是报错呢?那是服务端有 竞态资源 的可能性。尽管不太可能,但它是可能的,RPC服务器在发送给我们answer之后,但还没有发送an acknowledgement message之前死掉了。如果这种情况发生了,重启的RPC服务器将会再处理这个request. 那就是客户端为什么要优雅的处理两次responses. (可以对比第二个教程,会在接收端确认,如果接收端没有确认,之后队列会再次发送request,服务端需要再次处理)

Summary(总结)

我们的RPC像图中这样工作:

  • 当一个client启动时,它创建一个匿名的专用的callback queue.
  • 对于一个RPC request,client将会发送带有两个属性的message。ReplyTo,用来设置callback queue;并且CorrelationId,用来为每个request设置唯一的值。
  • Request会被发送到rpc_queue.
  • RPC worker(这里就是server)将会等待接收rpc_queue队列里的requests。当request出现时,它会做这个job,并且发送一个带结果的message给client,使用被ReplyTo属性命名的队列。
  • Client会等待callback queue的数据。当一个message出现时,它会检查CorrealtionId属性。如果它匹配request里的这个值(CorrealationId),将会返回response到这个应用(client)

代码

The Fibonacci task:

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, //声明queue
exclusive: false, autoDelete: false, arguments: null);
channel.BasicQos(, , false); //公平调度策略
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue: "rpc_queue", //接收消息
autoAck: false, consumer: consumer);
Console.WriteLine(" [x] Awaiting RPC requests"); consumer.Received += (model, ea) =>
{
string response = null; var body = ea.Body;
var props = ea.BasicProperties;
var replyProps = channel.CreateBasicProperties();
replyProps.CorrelationId = props.CorrelationId;//设置返回的CorrealationId 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,//发送响应到callback queue
basicProperties: replyProps, body: responseBytes);
channel.BasicAck(deliveryTag: ea.DeliveryTag,
multiple: false);
}
}; Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
} ///
/// 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.
///
private static int fib(int n)
{
if (n == || n == )
{
return n;
} return fib(n - ) + fib(n - );
}
}

RPCClient.cs

using System;using System.Collections.Concurrent;using System.Text;using RabbitMQ.Client;using RabbitMQ.Client.Events;
public class RpcClient
{
private readonly IConnection connection;
private readonly IModel channel;
private readonly string replyQueueName;
private readonly EventingBasicConsumer consumer;
private readonly BlockingCollection<string> respQueue = new BlockingCollection<string>();
private readonly IBasicProperties props;
public RpcClient()
{
var factory = new ConnectionFactory() { HostName = "localhost" }; connection = factory.CreateConnection();
channel = connection.CreateModel();
replyQueueName = channel.QueueDeclare().QueueName;
consumer = new EventingBasicConsumer(channel); props = channel.CreateBasicProperties();
var correlationId = Guid.NewGuid().ToString();
props.CorrelationId = correlationId;
props.ReplyTo = replyQueueName; consumer.Received += (model, ea) =>
{
var body = ea.Body;
var response = Encoding.UTF8.GetString(body);
if (ea.BasicProperties.CorrelationId == correlationId)
{
respQueue.Add(response);
}
};
} public string Call(string message)
{
var messageBytes = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(
exchange: "",
routingKey: "rpc_queue",
basicProperties: props,
body: messageBytes); channel.BasicConsume(
consumer: consumer,
queue: replyQueueName,
autoAck: true); return respQueue.Take(); ;
} public void Close()
{
connection.Close();
}
}
public 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();
}
}

参考网址:

https://www.rabbitmq.com/tutorials/tutorial-six-dotnet.html

RabbitMQ学习之RPC(6)的更多相关文章

  1. RabbitMQ学习笔记(六) RPC

    什么RPC? 这一段是从度娘摘抄的. RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的 ...

  2. 【c#】RabbitMQ学习文档(六)RPC(远程调用)

    远程过程调用(Remote Proceddure call[RPC]) (本实例都是使用的Net的客户端,使用C#编写) 在第二个教程中,我们学习了如何使用工作队列在多个工作实例之间分配耗时的任务. ...

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

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

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

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

  5. rabbitMQ学习笔记(七) RPC 远程过程调用

    关于RPC的介绍请参考百度百科里的关于RPC的介绍:http://baike.baidu.com/view/32726.htm#sub32726 现在来看看Rabbitmq中RPC吧!RPC的工作示意 ...

  6. RabbitMQ学习总结

    关于RabbitMQ是什么以及它的概念,不了解的可以先查看一下下面推荐的几篇博客 https://blog.csdn.net/whoamiyang/article/details/54954780 h ...

  7. 官网英文版学习——RabbitMQ学习笔记(一)认识RabbitMQ

    鉴于目前中文的RabbitMQ教程很缺,本博主虽然买了一本rabbitMQ的书,遗憾的是该书的代码用的不是java语言,看起来也有些不爽,且网友们不同人学习所写不同,本博主看的有些地方不太理想,为此本 ...

  8. RabbitMQ学习系列(四): 几种Exchange 模式

    上一篇,讲了RabbitMQ的具体用法,可以看看这篇文章:RabbitMQ学习系列(三): C# 如何使用 RabbitMQ.今天说些理论的东西,Exchange 的几种模式. AMQP协议中的核心思 ...

  9. RabbitMQ学习系列(三): C# 如何使用 RabbitMQ

    上一篇已经讲了Rabbitmq如何在Windows平台安装,还不了解如何安装的朋友,请看我前面几篇文章:RabbitMQ学习系列一:windows下安装RabbitMQ服务 , 今天就来聊聊 C# 实 ...

随机推荐

  1. django迁移脚本

    执行migrate报错的解决办法: 想知道migrate为什么报错,需要先了解migrate到底做了什么事情 migrate做了什么事情? 1.将相关的迁移脚本翻译成sql语句,然后在数据库中执行 2 ...

  2. eclipse 离线安装activiti

    1. 下载activiti离线安装包, activiti-designer-1.8.zip 2. 将上图4个jar包放在eclipse的features文件夹中 3. 打开eclipse-->H ...

  3. 为什么内核访问用户数据之前,要做access_ok?

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 原理 先看一段小视频,如果内核访问用户 ...

  4. 八、collection系列-----计数器、有序字典、默认字典、可命名元组、双向队列、单向队列一.计数器(对字典的扩展)

    一.计数器(对字典的扩展) 有如下一个字典: dic = {'k1':123,'k2':123,'k3':12} 统计12出现的次数,123出现的次数   1.统计出现次数 >>> ...

  5. 201871010126 王亚涛 《面向对象程序设计(java)》 第二周学习总结

    项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daizh/p ...

  6. JS中把其他类型转换成字符串的三种方法

    1.toString()方法 toString()方法返回的是相应值的字符串表现 数值.布尔值.对象和字符串值都有toString()方法,但是null和undefined值没有这个方法 例子: va ...

  7. Docker入门之安装Docker

    目录 目录 1 1. 前言 1 2. 创建网桥 2 3. 安装Docker 2 3.1. 二进制安装 3 3.1.1. 下载安装 3 3.1.2. 配置服务 3 3.1.3. 启动服务 4 3.2. ...

  8. NOIp初赛题目整理

    NOIp初赛题目整理 这个 blog 用来整理扶苏准备第一轮 csp 时所做的与 csp 没 有 关 系 的历年 noip-J/S 初赛题目,记录了一些我从不知道的细碎知识点,还有一些憨憨题目,不定期 ...

  9. B1003

    import re n = input() for i in range(int(n)): str = input() if re.match(r'A*PA+TA*',str): a = re.spl ...

  10. Web项目中使用Log4net 案例

    简介: 几乎所有的大型应用都会有自己的用于跟踪调试的API.因为一旦程序被部署以后,就不太可能再利用专门的调试工具了.然而一个管理员可能需要有一套强大的日志系统来诊断和修复配置上的问题. 经验表明,日 ...