RabbitMQ 官方NET教程(三)【发布/订阅】
上一篇博客中,我们实现了工作队列,并且我们的工作队列中的一个任务只会发给一个工作者,除非某个工作者未完成任务意外被杀死,会转发给另外的工作者。在这部分中,我们会做一些完全不同的事情 - 我们会向多个消费者传递信息。这种模式被称为“发布/订阅”。
为了说明这个模式,我们要建立一个简单的日志记录系统。它将包括两个程序 - 第一个将发出日志消息,第二个将接收并打印它们。
在我们的日志系统中,每一个运行的接收者程序都会收到日志。这样我们就可以实现一个接收者将接收到的数据写到硬盘上,与此同时,另一个接收者把接收到的消息展现在屏幕上。
本质上来说,就是已发布的日志消息将被广播到所有接收者。
转发器(Exchanges)
前面的博客中我们主要的介绍都是发送者发送消息给队列,接收者从队列接收消息。下面我们会引入Exchanges,展示RabbitMQ的完整的消息模型。
让我们快速了解我们在以前的教程中介绍的内容:
生产者是发送消息的用户应用程序。
队列是存储消息的缓冲区。
消费者是接收消息的用户应用程序。
RabbitMQ中的消息传递模型的核心思想是,生产者从不将任何消息直接发送到队列。实际上,生产者通常甚至不知道是否将消息传递到任何队列。
相反,生产者只能将信息发送到exchange
。交换是一件非常简单的事情。一方面,它收到来自生产者的消息,另一方将它们推送到队列。交换机必须准确知道接收到的消息如何处理。应该追加到特定队列吗?应该追加到很多队列吗?或者应该丢弃。其规则由exchange
类型定义。
有几种交换类型可用: direct, topic, headers 和fanout。 我们将重点关注最后一个 - fanout。 我们创建一个这种类型的exchange
,并称它为logs
:
channel.ExchangeDeclare("logs", "fanout");
fanout交换器非常简单。 正如您可以从名称猜出,它只是将所有收到的消息广播到所有知道的队列。 这正是我们需要的记录器。
列出交换机
要列出服务器上的交换机,您可以运行有用的rabbitmqctl:
sudo rabbitmqctl list_exchanges
在这个列表中会有一些amq.*
交换和默认(未命名)交换。 这些是默认创建的,但是不太可能需要使用它们。
默认交换
在本教程的前面部分,我们对交换没有任何了解,但仍然能够将消息发送到队列。 这是可能的,因为我们使用默认交换,我们通过空字符串("")
标识。
回想一下我们之前发布的消息:
var message = GetMessage(args);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "",
routingKey: "hello",
basicProperties: null,
body: body);
第一个参数是交换的名称。 空字符串表示默认或无名交换:消息通过路由Key指定的名称路由到队列(如果存在)。
现在,我们可以发布到我们命名的交换机:
var message = GetMessage(args);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "logs",
routingKey: "",
basicProperties: null,
body: body);
临时队列(Temporary queues)
前面我们使用的是具有指定名称的队列(记得hello
和task_queue
?)。 能够命名队列对我们而言至关重要 - 我们需要将工作者指向同一个队列。 当您想要在生产者和消费者之间共享队列时,给队列一个名字很重要。
但是我们的日志系统我们并不关心队列的名称。 我们希望接收到所有的日志消息,而不仅仅是它们的一部分。 而且我们也只对当前正在传递的数据的感兴趣。 要解决我们的需,要做两件事情。
首先,每当我们连接到Rabbit,我们需要一个新的空的队列。 为此,我们可以创建一个具有随机名称的队列,或者甚至更好 - 让服务器为我们选择一个随机队列名称。
其次,一旦消费者与Rabbit断开,队列应该被自动删除。
在.NET客户端中,当我们没有为queueDeclare()
提供参数时,我们创建了一个不可持续的,独占的,自动删除的队列,其中包含一个生成的名称:
var queueName = channel.QueueDeclare().QueueName;
此时,queueName包含一个随机队列名称。 例如,它可能看起来像amq.gen-JzTY20BRgKO-HjmUJj0wLg。
绑定(Bindings)
我们已经创建了一个扇出交换和一个队列。 现在我们需要告诉交换机发送消息到我们的队列。 交换和队列之间的关系称为绑定(binding)。
channel.QueueBind(queue: queueName,
exchange: "logs",
routingKey: "");
从现在开始,logs
交换器将追加消息到我们的队列。
列出绑定
你可以列出现有的绑定:
rabbitmqctl list_bindings
完整的例子
发出日志消息的生产者程序与上一个教程并没有太大的区别。 最重要的变化是我们现在想将消息发布到我们的logs
交换器,而不是无名的。 发送时需要提供一个routingKey
,但是对于fanout
交换来说,它的值被忽略。 这里是EmitLog.cs
文件的代码:
using System;
using RabbitMQ.Client;
using System.Text;
class EmitLog
{
public static void Main(string[] args)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection())
using(var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "logs", type: "fanout");
var message = GetMessage(args);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "logs",
routingKey: "",
basicProperties: null,
body: body);
Console.WriteLine(" [x] Sent {0}", message);
}
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
private static string GetMessage(string[] args)
{
return ((args.Length > 0)
? string.Join(" ", args)
: "info: Hello World!");
}
}
如你所见,建立连接后,我们声明交换。 此步骤是必须的,因为禁止发布到不存在的交换机。
如果没有任何队列绑定到交换机,消息将丢失,但是对我们来说没关系; 如果没有消费者正在收听,我们可以放心地放弃信息。
ReceiveLogs.cs
的代码:
using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
class ReceiveLogs
{
public static void Main()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection())
using(var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "logs", type: "fanout");
var queueName = channel.QueueDeclare().QueueName;
channel.QueueBind(queue: queueName,
exchange: "logs",
routingKey: "");
Console.WriteLine(" [*] Waiting for logs.");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] {0}", message);
};
channel.BasicConsume(queue: queueName,
noAck: true,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
}
按照教程一的安装说明生成EmitLogs
和ReceiveLogs
项目。
如果要将日志保存到文件,只需打开控制台并键入:
cd ReceiveLogs
dotnet run > logs_from_rabbit.log
如果您希望在屏幕上看到日志,则产生一个新的终端并运行:
cd ReceiveLogs
dotnet run
当然,要发射日志类型:
cd EmitLog
dotnet run
使用rabbitmqctl list_bindings
,您可以验证代码是否按照我们想要的方式创建绑定和队列。 运行两个ReceiveLogs.cs程序时,您应该看到如下所示:
sudo rabbitmqctl list_bindings
# => Listing bindings ...
# => logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue []
# => logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue []
# => ...done.
对结果的解释很简单:来自交换机logs
的数据转到具有服务器分配名称的两个队列。 这正是我们的意图。
RabbitMQ 官方NET教程(三)【发布/订阅】的更多相关文章
- RabbitMQ Go客户端教程3——发布/订阅
本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第三篇--发布/订阅. 这些教程涵盖了使用RabbitMQ创建消息传递 ...
- RabbitMQ入门学习系列(四) 发布订阅模式
发布订阅模式 什么时发布订阅模式 把消息发送给多个订阅者.也就是有多个消费端都完整的接收生产者的消息 换句话说 把消息广播给多个消费者 消息模型的核心 RabbitMQ不发送消息给队列,生产者也不知道 ...
- rabbitmq (三) 发布/订阅
rabbitmq的目的并不是让生产者把消息直接发到队列里面去, 这样不能实现解耦的目的,也不利于程序的扩展. 所以就有交换机(exchanges)的概念. 交换机有几种类型:direct, topic ...
- RabbitMQ 官方NET教程(六)【RPC】
在第二个教程中,我们学习了如何使用Work Queues在多个工作者之间分配耗时的任务. 但是如果我们需要在远程计算机上运行功能并等待结果怎么办? 那是一个不同的模式. 此模式通常称为远程过程调用或R ...
- RabbitMQ 官方NET教程(五)【Topic】
在上一个教程中,我们改进了我们的日志记录系统.我们使用direct类型转发器,使得接收者有能力进行选择性的接收日志,,而非fanout那样,只能够无脑的转发 虽然使用direct类型改进了我们的系统, ...
- RabbitMQ 官方NET教程(四)【路由选择】
在上一个教程中,我们构建了一个简单的日志记录系统. 我们能够广播日志消息给所有你的接收者. 在本教程中,我们将为其添加一个功能 - 我们将让日志接收者可以仅订阅一部分消息. 例如,我们将能够仅将关键的 ...
- RabbitMQ 官方NET教程(二)【工作队列】
这篇中我们将会创建一个工作队列用来在工作者(consumer)间分发耗时任务. 工作队列的主要任务是:避免立刻执行资源密集型任务和避免必须等待其完成.相反地,我们进行任务调度:我们把任务封装为消息发送 ...
- RabbitMQ 官方NET教程(一)【介绍】
本教程假定RabbitMQ已在标准端口(5672)上的localhost上安装并运行.如果使用不同的主机,端口或凭据,连接设置将需要调整. RabbitMQ是一个消息代理:它接受并转发消息. 您可以将 ...
- rabbitmq学习(四) —— 发布订阅
为了说明这种模式,我们将建立一个简单的日志系统.这个系统将由两个程序组成,第一个将发出日志消息,第二个将接收并处理日志消息.在我们的日志系统中,每一个运行的接收程序的副本都会收到日志消息. 交换器(E ...
随机推荐
- 怎么选择最适合自己的Linux培训机构?
Linux培训已经成为入门Linux的一个重要途径,它的优势在于学习知识的系统性.快速性和实用性.Linux培训毕业的学员大多数拥有较强的实战动手能力,能够较快上手,更符合企业需求. 不过,大部分同学 ...
- BZOJ 2096: [Poi2010]Pilots 单调队列
Code: #include<bits/stdc++.h> #define maxn 4000000 using namespace std; void setIO(string s) { ...
- Quartz.NET 定时任务使用
class Program { static void Main(string[] args) { StartJob(); Console.ReadKey(); } static void Start ...
- 发现是在IE6-IE9下,下列元素table,thead,tfoot,tbody,tr,col,colgroup,html,title,style,frameset的innerHTML属性是只读的
table ID="zhutiTable" html2="<tr></tr>": 的数据 setTableInnerHTML(docu ...
- 洛谷——P1759 通天之潜水
P1759 通天之潜水 题目背景 直达通天路·小A历险记第三篇 题目描述 在猴王的帮助下,小A终于走出了这篇荒山,却发现一条波涛汹涌的河拦在了自己的面前.河面上并没有船,但好在小A有n个潜水工具. ...
- C#学习笔记_10_设计模式&继承&多态
10_设计模式&继承&多态 设计模式 由前人总结的用来解决特定问题的解决方案 单例模式 在一个项目的不同模块中获取对象,获取到的是同一个对象 代码 继承 概念:如果多个类中具有相同的字 ...
- linux 头文件和库文件的设置
GCC/G++会查找系统默认的include和link的路径,以及自己在编译命令中指定的路径.自己指定的路径就不说了,这里说明一下系统自动搜索的路径. [1]include头文件路径 除了默认的/us ...
- java常见知识
在JSP页面获取当前项目名称的方法: 方法1: <%= this.getServletContext().getContextPath() %> 方法2: 使用EL表达式 ${pageCo ...
- hdu2000 ASCII码排序【C++】
ASCII码排序 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Su ...
- imu 和 private redo
在传统的undo管理模式中,Oracle对undo和data block是一视同仁.这样大致会有三种弊端: 1)事务开始时,存放事务表的段头不在内存,server process需要将此i/o上来 2 ...