RabbitMQ系列教程之三:发布/订阅(Publish/Subscribe)
(本教程是使用Net客户端,也就是针对微软技术平台的)
在前一个教程中,我们创建了一个工作队列。工作队列背后的假设是每个任务会被交付给一个【工人】。在这一部分我们将做一些完全不同的事情--我们将向多个【消费者】传递信息。这种模式被称为“发布/订阅”。
为了说明这种模式,我们将构建一个简单的日志系统。它将包括两个程序,第一个将发出日志消息,第二个将接收并打印它们。
在我们的日志系统中每个接收程序的运行副本都会得到消息。这样我们就可以运行一个接收者程序,将日志记录到磁盘;同时我们可以运行另一个接收者程序,并在屏幕上看到打印出来的日志。
从本质上讲,已发布的日志消息将被广播到所有的接收者程序。
1、消息交换机【Exchange】
在教程的前面部分,我们从队列中发送和接收消息。在RabbitMQ中,现在是时候引入全消息模型。
让我们快速看看我们以前的教程讲了什么:
【生产者】:就是一个用于发送消息的用户程序
【消费者】:就是一个用于接收和使用消息的用户程序
【队列】:是一个暂存消息的缓存区
RabbitMQ消息传递模型的核心思想是,【生产者】不直接发送任何信息到队列。事实上,【生产者】根本就不知道消息是否会被传送到任何队列。
相反,【生产者】只能发送消息到【消息交换机】。交换是件很简单的事。一方面它接收来自【生产者】的消息,另一方面是将接收到消息推送到队列中。【消息交换机】必须知道它如何处理接收消息的确切方法。是否应该发送到特定队列?它应该被发送到多个队列呢?或者它应该被丢弃。该规则由【消息交换机】的类型来定义。
这里有一些可用的【消息交换机】的类型:【Direct】直接,【Topic】主题,【Headers】标题和【Fanout】扇出。我们将集中关注最后一个-【Fanout】扇出。让我们创建一个这种类型的【消息交换机】,并给它命名为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);
第一个参数是【消息交换机】的名称。空字符串表示默认或未命名的消息交换机:消息会被路由到指定的routingkey名称的队列,如果它存在的话。
现在,我们可以发布到我们命名的【消息交换机】:
- var message = GetMessage(args);
- var body = Encoding.UTF8.GetBytes(message);
- channel.BasicPublish(exchange: "logs", routingKey: "", basicProperties: null, body: body);
2、临时队列
也许你还记得以前我们使用的队列所指定的名称(记得Hello和task_queue吗?)对我们来说,能够给一个队列指定名称是至关重要的--因为我们需要把【Worker】指向同一个队列。如果要在【生产者】和【消费者】之间共享队列,给队列命名是很重要的。
但这不是我们的日志记录器的情况。我们想听到所有的日志消息,而不仅仅是其中的一个子集。我们也只对当前刚刚收到的消息感兴趣,而不是对旧的。为了解决上述问题,我们需要做两件事。
首先,无论何时当我们连接到Rabbit的时候,我们都需要一个新的并且是空的队列。要做到这一点,我们可以创建一个具有随机名称的队列,或者,甚至更好一点-让服务器为我们选择一个随机队列名称。
其次,一旦我们断开与【消费者】的队列就应该自动删除该队列。
在.NET客户端中,当我们没有为queueDeclare()提供参数时,我们创建了一个具有生成名称的非持久,排他,自动删除队列:
- var queueName = channel.QueueDeclare().QueueName;
在这点上,QueueName包含随机队列名称。例如,它可能看起来像amq.gen-jzty20brgko-hjmujj0wlg。
3、绑定【Binding】

我们已经创建了一个【Fanout】类型的【消息交换机】和队列。现在我们需要告诉【消息交换机】向我们的队列发送消息。【消息交换机】和【队列】之间的关系称为绑定。
- channel.QueueBind(queue: queueName, exchange: "logs", routingKey: "");
从现在开始,日志的【消息交换机】就可以将消息推送到我们定义的队列中去了。
我们可以通过以下语句查看【binding】列表数据:
- rabbitmqctl list_bindings
4、把所有的代码整合到一起
【生产者】的程序,它发出的日志消息,看起来并没有和以前的教程有很大的不同。最重要的变化是,我们现在想发送的消息是到达我们指定名称的日志【消息交换机】,而不是无名的。我们在发送消息的时候需要提供一个routingkey表示的名称,但【Fanout】类型的【消息交换机】会容忽视该routingKey的值的。这里有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!");
- }
- }
(EmitLog.cs 的源码)
如你所见,在建立连接后,我们声明了【消息交换机】。此步骤是必要的,因为对非存在【消息交换机】的发送是被禁止的。
如果没有队列绑定到【消息交换机】,消息将会丢失,但对我们来说没有问题;如果没有【消费者】正在侦听,我们可以安全地丢弃消息。
以下是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();
- }
- }
- }
(ReceiveLogs.cs 的源码)
按照教程一的安装说明生成的EmitLogs和ReceiveLogs两个项目文件。
如果要将日志保存到文件,只需打开控制台并键入:
- cd ReceiveLogs
- dotnet run > logs_from_rabbit.log
如果你希望看到你的屏幕上的日志,生成一个新的终端和运行:
- cd ReceiveLogs
- dotnet run
当然,要发送日志类型:
- cd EmitLog
- dotnet run
使用rabbitmqctl list_bindings可以验证代码确实创建了我们想要的【绑定】和【队列】。运行两个ReceiveLogs.cs程序时,您应该看到如下所示:
- rabbitmqctl list_bindings
- # => Listing bindings ...
- # => logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue []
- # => logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue []
- # => ...done.
对结果的解释很简单:来自【消息交换机】日志的数据转到具有服务器分配名称的两个队列。 这正是我们的意图。
好了,终于翻译了第三篇教程了,翻译的不好,请见谅。如有大家英文比较好可以查看原文地址:http://www.rabbitmq.com/tutorials/tutorial-three-dotnet.html
RabbitMQ系列教程之三:发布/订阅(Publish/Subscribe)的更多相关文章
- RabbitMQ系列教程之三:发布/订阅(Publish/Subscribe)(转载)
RabbitMQ系列教程之三:发布/订阅(Publish/Subscribe) (本教程是使用Net客户端,也就是针对微软技术平台的) 在前一个教程中,我们创建了一个工作队列.工作队列背后的假设是每个 ...
- RabbitMQ入门教程(五):扇形交换机发布/订阅(Publish/Subscribe)
原文:RabbitMQ入门教程(五):扇形交换机发布/订阅(Publish/Subscribe) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...
- RabbitMQ学习总结 第四篇:发布/订阅 Publish/Subscribe
目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...
- RabbitMQ入门(3)——发布/订阅(Publish/Subscribe)
在上一篇RabbitMQ入门(2)--工作队列中,有一个默认的前提:每个任务都只发送到一个工作人员.这一篇将介绍发送一个消息到多个消费者.这种模式称为发布/订阅(Publish/Subscribe). ...
- rabbitmq系列三 之发布/订阅
1.发布/订阅 在上篇教程中,我们搭建了一个工作队列,每个任务只分发给一个工作者(worker).在本篇教程中,我们要做的跟之前完全不一样 —— 分发一个消息给多个消费者(consumers).这种模 ...
- RabbitMQ系列教程之七:RabbitMQ的 C# 客户端 API 的简介(转载)
RabbitMQ系列教程之七:RabbitMQ的 C# 客户端 API 的简介 今天这篇博文是我翻译的RabbitMQ的最后一篇文章了,介绍一下RabbitMQ的C#开发的接口.好了,言归正传吧. N ...
- RabbitMQ系列教程之六:远程过程调用(RPC)(转载)
RabbitMQ系列教程之六:远程过程调用(RPC) 远程过程调用(Remote Proceddure call[RPC]) (本实例都是使用的Net的客户端,使用C#编写) 在第二个教程中,我们学习 ...
- RabbitMQ系列教程之四:路由(Routing)(转载)
RabbitMQ系列教程之四:路由(Routing) (使用Net客户端) 在上一个教程中,我们构建了一个简单的日志系统,我们能够向许多消息接受者广播发送日志消息. 在本教程中,我们将为其添加一项功能 ...
- RabbitMQ系列教程之五:主题(Topic)(转载)
RabbitMQ系列教程之五:主题(Topic) (本实例都是使用的Net的客户端,使用C#编写),说明,中文方括号[]表示名词. 在上一个教程中,我们改进了我们的日志记录系统. 没有使用只能够进行虚 ...
随机推荐
- python3 selenium 如何处理异常情况
使用场景: 1.元素只有在某一特定情况下才会出现 2.元素定位不到 使用方法: try: except exceptions.NoSuchElementException: 举例说明: # _._ c ...
- OA办公系统功能真的越全越好?
4.原文:http://www.jiusi.net/detail/472__776__4000__1.html 关键词:oa系统,OA办公系统 OA办公系统功能真的越全越好? 很多企业在选择OA办公系 ...
- zabbix_server表面启动成功,但是没有进程
[root@iZ25m1box8cZ zabbix]# /etc/init.d/zabbix_server start Starting zabbix_server (via systemctl): ...
- tail命令
tail命令用来取文件后几行,默认显示后10行.有多个FILE,每个都带有一个头文件名称. 语法: tail [OPTION]... [FILE]... 选项: -n#:取文件后#行,n可省略: -c ...
- NSA永恒之蓝病毒,如何通过360工具修复?
简介: NSA武器库的公开被称为是网络世界"核弹危机",其中有十款影响Windows个人用户的黑客工具,包括永恒之蓝.永恒王者.永恒浪漫.永恒协作.翡翠纤维.古怪地鼠.爱斯基摩卷. ...
- PHP 中级内容
1.面向对象编程(OOP) 2.模板引擎(smarty) 3.MYSQL(中级操作) 数据库抽象层(PDO): 4.Ajax(异步刷新) 5.Js框架(jQuery) Xml+JSON数据 ...
- Ubuntu设置终端相对短路径
这个设置相对实际上是比较简单的.在自己的家目录打开.bashrc 找到PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$' 只需要将w修改为大写W保存, ...
- 【JAVAWEB学习笔记】02_HTML&CSS
//--------------纯文本版------------------------------------------------- 今日新增单词(更多属性查看手册) form:表单 (要提交的 ...
- Java调用IDL方法总结
Java调用IDL方法总结 Java调用IDL程序,需要先在java中加载IDL的java包(javaidlb.jar),该包不需要下载,在IDL的安装目录中可以直接找到(C:\Program Fil ...
- 《Android进阶》之第二篇 launcher
public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params, boolean ...