我们会做一些改变,就是把一个消息发给多个消费者,这种模式称之为发布/订阅(类似观察者模式)。

为了验证这种模式,我们准备构建一个简单的日志系统。这个系统包含两类程序,一类程序发动日志,另一类程序接收和处理日志。

在我们的日志系统中,每一个运行的接收者程序都会收到日志。然后我们实现,一个接收者将接收到的数据写到硬盘上,与此同时,另一个接收者把接收到的消息展现在屏幕上。本质上来说,就是发布的日志消息会转发给所有的接收者。

  1、转发器(Exchanges)

  RabbitMQ消息模型的核心理念是生产者永远不会直接发送任何消息给队列,一般的情况生产者甚至不知道消息应该发送到哪些队列。

  相反的,生产者只能发送消息给转发器(Exchange)。转发器是非常简单的,一边接收从生产者发来的消息,另一边把消息推送到队列中。转发器必须清楚的知道消息如何处理它收到的每一条消息。是否应该追加到一个指定的队列?是否应该追加到多个队列?或者是否应该丢弃?这些规则通过转发器的类型进行定义。

  下面列出一些可用的转发器类型:

  Direct

  Topic

  Headers

  Fanout

  目前我们关注最后一个fanout,声明转发器类型的代码:

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

  fanout类型转发器特别简单,把所有它介绍到的消息,广播到所有它所知道的队列。不过这正是我们前述的日志系统所需要的。

  2、匿名转发器(nameless exchange)

  前面说到生产者只能发送消息给转发器(Exchange),我们仍然可以发送和接收消息。这是因为我们使用了一个默认的转发器,它的标识符为””。之前发送消息的代码:

 channel.basicPublish("", QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());

  第一个参数为转发器的名称,我们设置为”” : 如果存在routingKey(第二个参数),消息由routingKey决定发送到哪个队列。

  现在我们可以指定消息发送到的转发器:

 channel.basicPublish( "logs","", null, message.getBytes());

  3、临时队列(Temporary queues)

  前面的博客中我们都为队列指定了一个特定的名称。能够为队列命名对我们来说是很关键的,我们需要指定消费者为某个队列。当我们希望在生产者和消费者间共享队列时,为队列命名是很重要的。
  不过,对于我们的日志系统我们并不关心队列的名称。我们想要接收到所有的消息,而且我们也只对当前正在传递的数据的感兴趣。为了满足我们的需求,需要做两件事:
  第一, 无论什么时间连接到Rabbit我们都需要一个新的空的队列。为了实现,我们可以使用随机数创建队列,或者更好的,让服务器给我们提供一个随机的名称。
  第二, 一旦消费者与Rabbit断开,消费者所接收的那个队列应该被自动删除。
  Java中我们可以使用queueDeclare()方法,不传递任何参数,来创建一个非持久的、唯一的、自动删除的队列且队列名称由服务器随机产生。

 String queueName = channel.queueDeclare().getQueue();

  一般情况这个名称与amq.gen-JzTY20BRgKO-HjmUJj0wLg 类似。

  4、绑定(Bindings)

   

  我们已经创建了一个fanout转发器和队列,我们现在需要通过binding告诉转发器把消息发送给我们的队列。
  channel.queueBind(queueName, “logs”, ””)参数1:队列名称 ;参数2:转发器名称

  5、完整的例子
  
  发送端:
 public class EmitLog
{
private final static String EXCHANGE_NAME = "ex_log"; public static void main(String[] args) throws IOException
{
// 创建连接和频道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 声明转发器和类型
channel.exchangeDeclare(EXCHANGE_NAME, "fanout" ); String message = new Date().toLocaleString()+" : log something";
// 往转发器上发送消息
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); channel.close();
connection.close(); } }

  接收端1:

 public class ReceiveLogsToSave
{
private final static String EXCHANGE_NAME = "ex_log"; public static void main(String[] argv) throws java.io.IOException,
java.lang.InterruptedException
{
// 创建连接和频道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 创建一个非持久的、唯一的且自动删除的队列
String queueName = channel.queueDeclare().getQueue();
// 为转发器指定队列,设置binding
channel.queueBind(queueName, EXCHANGE_NAME, ""); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); QueueingConsumer consumer = new QueueingConsumer(channel);
// 指定接收者,第二个参数为自动应答,无需手动应答
channel.basicConsume(queueName, true, consumer); while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody()); print2File(message);
} } private static void print2File(String msg)
{
try
{
String dir = ReceiveLogsToSave.class.getClassLoader().getResource("").getPath();
String logFileName = new SimpleDateFormat("yyyy-MM-dd")
.format(new Date());
File file = new File(dir, logFileName+".txt");
FileOutputStream fos = new FileOutputStream(file, true);
fos.write((msg + "\r\n").getBytes());
fos.flush();
fos.close();
} catch (FileNotFoundException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
}
}
}

  随机创建一个队列,然后将队列与转发器绑定,然后将消费者与该队列绑定,然后写入日志文件。

 public class ReceiveLogsToConsole
{
private final static String EXCHANGE_NAME = "ex_log"; public static void main(String[] argv) throws java.io.IOException,
java.lang.InterruptedException
{
// 创建连接和频道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 创建一个非持久的、唯一的且自动删除的队列
String queueName = channel.queueDeclare().getQueue();
// 为转发器指定队列,设置binding
channel.queueBind(queueName, EXCHANGE_NAME, ""); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); QueueingConsumer consumer = new QueueingConsumer(channel);
// 指定接收者,第二个参数为自动应答,无需手动应答
channel.basicConsume(queueName, true, consumer); while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'"); } } }

  随机创建一个队列,然后将队列与转发器绑定,然后将消费者与该队列绑定,然后打印到控制台。

   现在把两个接收端运行,然后运行3次发送端:

  输出结果:

  发送端:

[x] Sent '2014-7-10 16:04:54 : log something'

[x] Sent '2014-7-10 16:04:58 : log something'

[x] Sent '2014-7-10 16:05:02 : log something'

  接收端1:

  接收端2:

[*] Waiting for messages. To exit press CTRL+C
 [x] Received '2014-7-10 16:04:54 : log something'
 [x] Received '2014-7-10 16:04:58 : log something'
 [x] Received '2014-7-10 16:05:02 : log something'

  这个例子实现了我们文章开头所描述的日志系统,利用了转发器的类型:fanout。

  参考文档:http://blog.csdn.net/lmj623565791/article/details/37657225

RabbitMQ 发布/订阅的更多相关文章

  1. RabbitMQ 发布订阅持久化

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

  2. RabbitMQ 发布订阅

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

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

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

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

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

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

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

  6. 3.rabbitmq 发布/订阅

    1. 发布者 #coding:utf8 import pika import json import sys message = ''.join(sys.argv[1:]) or "hell ...

  7. 【译】RabbitMQ:发布-订阅(Publish/Subscribe)

    在前一篇教程中,我们创建了一个工作队列,我们假设在工作队列后的每一个任务都只被调度给一个消费者.在这一部分,我们将做一些完全不一样的事情,调度同一条消息给多个消费者,也就是有名的“发布-订阅”模式.为 ...

  8. rabbitmq消息队列——"发布订阅"

    三."发布订阅" 上一节的练习中我们创建了一个工作队列.队列中的每条消息都会被发送至一个工作进程.这节,我们将做些完全不同的事情--我们将发送单个消息发送至多个消费者.这种模式就是 ...

  9. RabbitMQ入门教程——发布/订阅

    什么是发布订阅 发布订阅是一种设计模式定义了一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象.这个主题对象在自身状态变化时,会通知所有的订阅者对象,使他们能够自动更新自己的状态. 为了描述这种 ...

随机推荐

  1. SCCM 客户端的修复

    1. Stopping the SMS Agent Host service (net stop ccmexec) 2. Stopping the WMI service (net stop winm ...

  2. Linux 下从头再走 GTK+-3.0 (五)

    实践中表明,纯粹利用 gtk 函数来创建 UI 是很繁琐的事,需要编写很多代码.怎样才能快速统一的建立 UI 布局呢? 可喜的是 GTK 提供了一个 GtkBuilder 用于快速创建界面.它读取一个 ...

  3. web报表移动端如何进行移动设备绑定与撤销

    场景需求描述 为了增强移动端的登录机制验证,保证数据的安全性,报表工具FineReport提供了移动设备绑定的功能,每个系统用户在使用移动端连接系统的时,需要管理员授权,将用户的移动设备与系统绑定起来 ...

  4. 理解 QEMU/KVM 和 Ceph(3):存储卷挂接和设备名称

    本系列文章会总结 QEMU/KVM 和 Ceph 之间的整合: (1)QEMU-KVM 和 Ceph RBD 的 缓存机制总结 (2)QEMU 的 RBD 块驱动(block driver) (3)存 ...

  5. [转]C#网络编程(订立协议和发送文件) - Part.4

    本文转自:http://www.tracefact.net/CSharp-Programming/Network-Programming-Part4.aspx 源码下载:http://www.trac ...

  6. JDK环境变量详细讲解

    首先,你应该已经安装了 java 的 JDK 了,笔者安装的是:jdk-8u65-windows-x64 下载地址: http://www.oracle.com/technetwork/java/ja ...

  7. 周五了啦啦啦啦-LAMP+PHP‘s OOP

    hi 周五咯~~ 1.LAMP配置完结篇 五.LAMP配置环境优化 5.4 虚拟主机工作原理 apache的虚拟主机.virtual-host 用不同的域名访问不同的目录——手动模拟dns 修改hos ...

  8. For循环案例---九九乘法表

    概述:先创建一个Print99类,类中创建5个方法,分别为Test9901.Test9902.Test9903.Test9904.Test9905,分别打印出不同形状的九九乘法表,该类创建完成后再创建 ...

  9. 第18章 图元文件_18.2 增强型图元文件(emf)(1)

    18.2 增强型图元文件(emf) 18.2.1 创建并显示增强型图元文件的步骤 (1)创建:hdcEMF = CreateEnhMetaFile(hdcRef,szFilename,lpRect,l ...

  10. 转:[版本控管]TortoiseSVN 使用,抓出兇手,使用 Blame 查看每一行最後修改的人是誰

    类似的官方的使用手段有: https://tortoisesvn.net/docs/nightly/TortoiseSVN_zh_CN/tsvn-dug-blame.html from: http:/ ...