在前一篇教程中,我们创建了一个工作队列,我们假设在工作队列后的每一个任务都只被调度给一个消费者。在这一部分,我们将做一些完全不一样的事情,调度同一条消息给多个消费者,也就是有名的“发布-订阅”模式。为了阐述这种模式,我们将构建一个简单的日志系统。该系统将由两部分组成:一部分发送日志消息,另一部分接收并且打印日志消息,在这个日志系统中,每一份运行着的接收程序都将会收到消息。这样我们可以运行一个接收者把日志写入到磁盘中,同时可以运行另一个接收者将日志打印到显示器上面。也就是说,发布的日志消息会被广播到所有的接收者。

交换器

在前面的教程中,我们发送消息到队列,然后从队列中接收消息。现在开始介绍RabbitMQ完整的消息模式。

让我们快速的复习一下在前面的教程中讲过的内容:

  • 生产者是一个发送消息的应用程序。
  • 队列是存储消息的缓存。
  • 消费者是一个接收消息的应用程序。

RabbitMQ消息模式的核心是生产者从不直接发送消息到队列。事实上,生产者往往不知道他产生的消息会被分发到哪些队列,它只能将消息发送到一个交换器。交换器非常简单,它一方面从生产者接收消息,另一方面又将消息压入队列中。交换器必须清楚的知道要用接收到的消息做什么,是应当追加到某个指定的队列?或者追加到很多队列?或者应当丢弃?要完成这些的规则都被定义在交换器的类型中。

有几种可用的交换器类型:direct、topic、headers和fanout。本文主要关注最后一种类型:fanout,让我们创建一个这种类型的交换器,命名为logs:

 channel.ExchangeDeclare("logs", "fanout");

类型为fanout的交换器非常简单,顾名思义,它会广播所有收到的消息到它知道的所有的队列,而这也正是我们的日志系统所需要的。

交换器清单

为了展示服务器上交换器的清单,你可以运行在任何时候都特别有用的rabbitmqctl:

 $ sudo rabbitmqctl list_exchanges
Listing exchanges ...
direct
amq.direct direct
amq.fanout fanout
amq.headers headers
amq.match headers
amq.rabbitmq.log topic
amq.rabbitmq.trace topic
amq.topic topic
logs fanout
...done.

在清单里,有一些amp.*样式的交换器和一个默认(未命名)的交换器,这些都是默认创建的,但并不是说你马上就需要使用它们。

匿名交换器

在前面的教程中我们并不知晓交换器的任何信息,但是任然可以将消息发送到队列中,那是因为我们使用了默认的交换器,使用空字符串表示("")。

回忆一下之前是如何发布消息的:

 var message = GetMessage(args);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "",
routingKey: "hello",
basicProperties: null,
body: body);

第一个参数就是交换器的名称,空字符串指代的是默认交换器或者是匿名交换器,如果队列存在,消息将通过指定的routingKey路由到队列。

现在我们可以将消息发布到上面定义的命名交换器了:

 var message = GetMessage(args);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "logs",
routingKey: "",
basicProperties: null,
body: body);

临时队列

你或许还记得我们之前使用的有指定名称的队列(还记得hello和task_queue么?)。能为队列命名对我们来说是至关重要的,我们需要指定给消费者相同的队列。当你想在生产者和消费者间共享队列时,给队列指定一个名字就显得特别重要了。

但是这并不是我们日志系统的问题。我们希望能监听到所有消息,而不仅仅是其中一个子集;我们对当前流入的消息感兴趣而不是之前的旧信息。为了解决这个问题,我们需要做两件事:第一、无论何时连接到RabbitMQ,我们需要一个新的空队列,为此我们可以创建一个拥有随机名称的队列或者更好的是直接让RabbitMQ服务替我们生成一个随机名称;第二、一旦消费者断开连接,队列应当被自动删除。

在.NET 客户端,我们通过提供无参数的QueueDeclare()函数可以创建一个不持久化、独占的、自动删除的拥有随机名称的队列:

 var queueName = channel.QueueDeclare().QueueName;

这样queueName就是一个随机的队列名称,看起来会是这样的:amq.gen-JzTY20BRgKO-HjmUJj0wLg。

绑定

我们已经创建了一个fanout类型的交换器和一个队列,现在需要告诉交换器把消息发送到我们的队列。交换器和队列的关系就叫做绑定。

 channel.QueueBind(queue: queueName,
exchange: "logs",
routingKey: "");

到目前为止,交换器logs将能添加消息到我们的队列中了。

绑定清单

你可以通过rabbitmqctl list_bingdings命令查看绑定清单。

组合在一起

发送日志的生产者程序和之前教程里面的没有太多不同,最重要的改变是现在我们希望将消息发送到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 > )
? string.Join(" ", args)
: "info: Hello World!");
}
}

如你所见,在创建链接之后我们申明了交换器,这一步用于禁止发布到不存在的交换器是很有必要的。如果没有队列绑定到交换器发布的消息将会丢失,这是没有问题的;如果没有消费者监听消息,我们可以安全的销毁它。

ReceiveLog.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();
}
}
}

像之前那样编译,工作就完成了。

 $ csc /r:"RabbitMQ.Client.dll" EmitLogs.cs
$ csc /r:"RabbitMQ.Client.dll" ReceiveLogs.cs

如果你想将日志保存到文件中,打开控制台然后输入:

 $ ReceiveLogs.exe > logs_from_rabbit.log

如果你想在屏幕上看到日志,打开一个新的终端,执行下面的代码:

 $ ReceiveLogs.exe

当然,发送日志输入:

 $ EmitLog.exe

使用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发送到两个服务自动指定名称的队列,这正是我们之前预期的。

要了解如何监听消息的子集,让我们进入下一篇。

原文链接:http://www.rabbitmq.com/tutorials/tutorial-three-dotnet.html

【译】RabbitMQ:发布-订阅(Publish/Subscribe)的更多相关文章

  1. RabbitMQ学习总结 第四篇:发布/订阅 Publish/Subscribe

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

  2. RabbitMQ入门(3)——发布/订阅(Publish/Subscribe)

    在上一篇RabbitMQ入门(2)--工作队列中,有一个默认的前提:每个任务都只发送到一个工作人员.这一篇将介绍发送一个消息到多个消费者.这种模式称为发布/订阅(Publish/Subscribe). ...

  3. RabbitMQ入门教程(五):扇形交换机发布/订阅(Publish/Subscribe)

    原文:RabbitMQ入门教程(五):扇形交换机发布/订阅(Publish/Subscribe) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...

  4. RabbitMQ 发布订阅持久化

    RabbitMQ是一种重要的消息队列中间件,在生产环境中,稳定是第一考虑.RabbitMQ厂家也深知开发者的声音,稳定.可靠是第一考虑,为了消息传输的可靠性传输,RabbitMQ提供了多种途径的消息持 ...

  5. RabbitMQ 发布订阅

    互联网公司对消息队列是深度使用者,因此需要我们了解消息队列的方方面面,良好的设计及深入的理解,更有利于我们对消息队列的规划. 当前我们使用消息队列中发现一些问题: 1.实际上是异步无返回远程调用,由发 ...

  6. RabbitMQ发布订阅实战-实现延时重试队列

    RabbitMQ是一款使用Erlang开发的开源消息队列.本文假设读者对RabbitMQ是什么已经有了基本的了解,如果你还不知道它是什么以及可以用来做什么,建议先从官网的 RabbitMQ Tutor ...

  7. RabbitMQ学习之Publish/Subscribe(3)

    上一个教程中,我们创建了一个work queue. 其中的每个task都会被精确的传送到一个worker. 这节,我们将会讲把一个message传送到多个consumers. 这种模式叫做publis ...

  8. RabbitMQ 发布/订阅

    我们会做一些改变,就是把一个消息发给多个消费者,这种模式称之为发布/订阅(类似观察者模式). 为了验证这种模式,我们准备构建一个简单的日志系统.这个系统包含两类程序,一类程序发动日志,另一类程序接收和 ...

  9. RabbitMQ 发布订阅-实现延时重试队列(参考)

    RabbitMQ消息处理失败,我们会让失败消息进入重试队列等待执行,因为在重试队列距离真正执行还需要定义的时间间隔,因此,我们可以将重试队列设置成延时处理.今天参考网上其他人的实现,简单梳理下消息延时 ...

  10. .Net下RabbitMQ发布订阅模式实践

    一.概念AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计.消息中间件主要用于组件之间的解耦,消息的发 ...

随机推荐

  1. 2015.05.12:json的常用处理方式

    1:json的介绍:json常用于前台与后台的数据传输  传递时需将json对象转换为json字符 JSON.stringify(); 2:json格式的查看应用:JsonView 3:后台获取到js ...

  2. RabbitMQ消息机制广播分发

    public static void SendMessage() { var factory = new ConnectionFactory(); factory.HostName = "1 ...

  3. linux yum 命令

    linux yum 命令 yum( Yellow dog Updater, Modified)是一个在Fedora和RedHat以及SUSE中的Shell前端软件包管理器. 基於RPM包管理,能够从指 ...

  4. 如何在tpl模版的div块中加ztree

    ld-ztree.tpl <div class="ld-ztree-container"> <div class="ld-ztree-header te ...

  5. 史上最强大的40多个纯CSS绘制的图形

    Square(正方形) #square { width: 100px; height: 100px; background: red; } Rectangle(矩形) #rectangle { wid ...

  6. MYSQL性能优化分享(分库分表)

    1.分库分表 很明显,一个主表(也就是很重要的表,例如用户表)无限制的增长势必严重影响性能,分库与分表是一个很不错的解决途径,也就是性能优化途径,现在的案例是我们有一个1000多万条记录的用户表mem ...

  7. AP创建会计科目

    一. 创建会计科目的途径 1. 在发票工作台对单张发票进行创建科目: 2. 提交“创建会计科目”并发请求,对所有已经验证但尚未创建会计科目的发票进行创建会计科目. 二. 对单张发票创建会计科目 发票在 ...

  8. 使用HttpURLConnection下载图片

    import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.Ht ...

  9. 关于iOS开发证书的一些总结(很有用)

    今天出了个问题,具体是这样的,我把本地的钥匙传里面的各种东西全部清空了,结果出现了各种不可预料到问题.花了一下午的时间反复的测试,终于把证书的一些问题理顺,然后在这里做一些总结. 先看张图片: 其中, ...

  10. hive中分析函数window子句

    hive中有些分析函数功能确实很强大,在和sum,max等聚合函数结合起来能实现不少功能. 直接上代码演示吧 原始数据 channel1 2016-11-10 1 channel1 2016-11-1 ...