发布/订阅

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

为了说明这个模式,我们将要创建一个简单的日志系统,一个负责发布消息,另外一个负责接收打印他们.

在我们的日志系统中,每一个运行中的接收者副本将都会获得消息,这种方式可以让我们在运行一个接收者直接把消息保存在磁盘的同时,另外一个消费者可以把消息打印到屏幕上.

本质上,发布一个日志消息将会广播给所有的接收者

交换机(Exchanges)

在之前的文章中,我们接受和发送消息都是通过一个队列来完成了,现在是时候引入RabbitMQ的全部工作模型了.

让我们快速回忆一下之前涉及到的模型

--生产者(发布者),是一个负责发送消息的用户应用程序.

--队列,负责存储消息

--消费者(接收者),负责接收消息的用户程序.

RabbitMQ的核心思想是生产者永远不会直接把消息发送给队列,事实上生产者甚至经常不知道一个发出去的消息是否可以有队列去接收它.

相应的,生产者只能消息发送给交换机,交换机的工作机制非常简单,一方面它从生产者那里接收到消息,另一方面它会把消息发送给相应的队列上.交换机必须要知道怎么处理接收到的消息,它应该被放入一个特殊的队列吗?它是否应该被放入多个队列?或者它是否需要被忽略.

处理这工作的方式是通过交换机类型来实现的.

这里有几个可用的交换机类型:direct,topic,headers,fanout 我们将会关注最后一个(fanout),让我们创建一个fanout的交换机,名字叫做'logs'

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

这个fanout的交换机功能非常简单(你也许已经从名字中猜到了他的方式),把接收到的消息广播给所有已知的队列,这个这是我们的日志系统需要的.

列出RabbitMQ已添加的交换机:

cmd:rabbitmqctl list_exchanges

无命名的交换机:在之前的案例中我们对于交换机一无所知,但是仍然可以把消息发送到队列上,这是因为我们使用的是一个默认的交互机,名字为空(""),回顾一下我们之前发送消息的方式

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 吗),给一个队名命名是严格的,我们需要执行者连接的同样的队列来工作,当你想在生产者和消费者之间共享队列的时候指定一个队列名是非常重要的.但是我们的日志系统则不在此列,

我们想要监听到所有的日志消息,而不仅仅是他们的子集,我们也仅仅对当前正在流转的消息感兴趣,而不是老的消息,结局这个问题我们需要2件事情.

首先,无论何时我们连接到队列,我们都需要一个新鲜的,空的队列,为了实现这个目标我们可以每次创建一个随机名称的队列,或者更加便捷的方式--让服务为我们的队列随机命名.

第二,一旦我们断开到消费者到队列的连接,我们需要自动删除队列.

在.Net客户端,我们使用无参的queueDeclare()方法来创建一个随机命名的非持久的,自动删除的排他队列.

var queueName = channel.QueueDeclare().QueueName;

queueName就是一个随机的队列名,如:amq.gen-JzTY20BRgKO-HjmUJj0wLg.

绑定

我们已经创建了一个fanout的交换机和一个队列,现在我们需要告诉我们交换机发送消息到我们的队列,交换机和队列之间的关系叫做绑定.

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

从现在开始logs 交换机将会把消息放入我们的队列当中.

列出队列cmd: rabbitmqctl list_bindings

汇总

负责发送消息的生产者可之前案例基本上是一样的,最大的不同是我们将消息发送到了我们的命名队列logs上而不是默认的队列上,发送的时候我们需要使用routingKey,但是它的值是被fanout交换机忽略的.

EmitLog.cs

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!");
}
}

正如你看到的,我们在建立连接之后创建了一个队列,这一步是必须的,因为发送到一个不存在的交换机是不被允许的。

当队列还没有绑定到交换机是发送的消息将会丢失,但是这对我们日志系统来说没有问题,当没有消费者监听时我们可以安全的忽略这个消息。

ReceiveLogs.cs:

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

同时运行两个receive,可以看到两个接收端可以同时接收到一个消息。

RabbitMQ 原文译03--发布和订阅的更多相关文章

  1. Part1.2 、RabbitMQ -- Publish/Subscribe 【发布和订阅】

    python 目录 (一).交换 (Exchanges) -- 1.1 武sir 经典 Exchanges 案例展示. (二).临时队列( Temporary queues ) (三).绑定(Bind ...

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

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

  3. RabbitMQ 原文译1.1--HelloWord

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

  4. RabbitMQ 原文译06--Remote procedure call(RPC)

    在第三篇文章中, 我们学习了怎么使用队列在多了消息消费者当中进行耗时任务轮询. 但是如果我们想要在远程电脑上运行一个方法,然后等待其执行结果,这就是一个不同的场景,这种就是我们一般讲的RPC(远程过程 ...

  5. RabbitMQ 原文译05--Topics

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

  6. RabbitMQ 原文译04--路由

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

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

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

  8. 文成小盆友python-num12 Redis发布与订阅补充,python操作rabbitMQ

    本篇主要内容: redis发布与订阅补充 python操作rabbitMQ 一,redis 发布与订阅补充 如下一个简单的监控模型,通过这个模式所有的收听者都能收听到一份数据. 用代码来实现一个red ...

  9. Python-RabbitMQ消息队列的发布与订阅

    RabbitMQ消息队列的发布与订阅类似于广播,一端发送消息,多个客户端可以同时接收到消息 fanout:所有绑定到exchange的queue都可以接收消息 消息发布端 # -*- coding:u ...

随机推荐

  1. Java HashMap实例源码分析

    引言 HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢? 一 Entry Entry是Map接口中的一个内部接口,它是实现键值对存储关键.在HashMap中,有Entry的实现 ...

  2. 问题-在TreeView使用时,发现选中的树节点会闪烁或消失

    问题:在工程中选中一个树节点,鼠标焦点在树上,做某种操作时发现选中的点会消失?原因:如果只是BeginUpdate后,没有调用EndUpdate,树会全空.应该是BeginUpdate方法会刷新树,但 ...

  3. A Tour of Go Methods continued

    In fact, you can define a method on any type you define in your package, not just structs. You canno ...

  4. 低级错误之Hbm中类型不一致错误

    Myeclipse将数据库中的长整形生成为Bigdecimal类型,实际应该生成为Long.导致保存的时候报错.

  5. Java面试题集(51-70)

    Java程序员面试题集(51-70) 51.类ExampleA 继承Exception,类ExampleB 继承ExampleA. 有如下代码片断: try{ thrownew ExampleB(“b ...

  6. [转]Log4Net中配置文件的解释

    FROM:http://www.cnblogs.com/kissazi2/p/3392605.html 一个完整的配置文件的例子如下所示 <log4net> <!-- 错误日志类-- ...

  7. Android权限设置android.permission完整列表

    android.permission.ACCESS_CHECKIN_PROPERTIES允许读写访问"properties”表在checkin数据库中,改值可以修改上传( Allows re ...

  8. Unit Testing a zend-mvc application

    Unit Testing a zend-mvc application A solid unit test suite is essential for ongoing development in ...

  9. 第一章 00 StringUtil.cpp和StringUtil.hh分析

    /* * StringUtil.hh * * Copyright 2002, Log4cpp Project. All rights reserved. * * See the COPYING fil ...

  10. [转]javascript指定事件处理程序包括三种方式:

    javascript指定事件处理程序包括三种方式: (1):DOM0级事件处理程序 如: 代码如下: var btn=document.getElementById("mybtn" ...