Publish/Subscribe

在上一节我们创建了一个work queue。背后的设想为每个任务被分发给明确的消费者。这节内容我们将做一些完全不同的事情 -- 我们将发送一条消息给多个消费者。这种模式被称为“发布/订阅”。

为了描述这种模式,我们来构建一个简单的日志系统。它包含两个程序 -- 第一个将会发送日志消息,第二个接收并打印。在我们的日志系统中,所有的正在运行的接收程序都会收到消息。这样我们可以运行一个接收程序,将日志定向到磁盘;同时可以运行另外的接收程序可以从屏幕上看到日志。

本质上,发布的所有日志消息会被广播给所有的接受者。

交换机

在前几节内容中,我们都是从一个队列中发送和接收消息。现在是时候介绍RabbitMQ的完整消息模型了。

快速回顾前面章节:

  • 一个生产者是一个发送消息的用户应用
  • 一个队列是一个存放消息的缓冲区
  • 一个消费者是一个接收消息的用户应用

RabbitMQ消息模型的核心思想是,生产者从来不会直接发送消息给一个队列。确切的说,大多数情况下,生产者根本不知道它的消息将会发送到哪个队列。

事实是,生产者只能发送消息给一个交换机(exchange)。交换机是一个很简单的概念。一方面它接收生产者的消息,另一方面它推送消息到队列中。但是交换机必须明确知道自己要对接收到的消息进行何种处理:是添加到制定队列?还是添加到所有的队列?抑或是将之丢弃?这些规则由交换机的类型来定义。

有许多可供选择的交换机类型:direct, topic, headers, fanout. 我们集中在fanout上讲解。创建一个fanout类型的交换机,称它为logs:

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

这个交换机非常简单。它会广播所有接收到的消息给所有它的已知队列。这就是我们logger程序所需要的。

Listing exchanges

可以使用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.*的交换机和默认(未命名)的交换机。它们都是默认被创建的。

Nameless exchange

之前的章节中我们对交换机一无所知,但却仍然可以发送消息到队列中。这很可能是因为我们使用了默认的交换机,我们用空字符串("")标识了它。

回想我们如何发布一条消息的:

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

第一个参数就是交换机的名字。空字符串表示默认或未命名的交换机:消息根据路由Key指定的队列名称被路由到队列。

现在我们可以发布到我们自己命名的交换机:

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

临时队列

你可能还记得我们之前使用的队列都是有一个指定的名称的(比如hello和task_queue)。给队列命名对我们来说至关重要 -- 我们需要将消费者指向相同的队列。当你想在生产者和消费者之间分享队列的时候,给队列一个名字非常重要。

但我们的日志程序不是这样的。我们希望监听所有的日志消息,而不仅仅是其中的一部分。我们也仅仅对当前的流动消息感兴趣,而不是老的消息。解决这个问题需要下面两件事:

第一,无论何时我们连接到RabbitMQ时,都需要一个新的空的队列。要做到这一点我们可以创建一个随机名称的队列,或者更好一点的方式 - 让服务器为我们选择一个随机队列名。

第二,一旦我们断开生产者的连接,队列应该被自动删除。

在Java客户端,当我们调用无参的queueDeclare()方法,我们将创建一个非持久化的,唯一的,自动删除的并且随机名称的队列。

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

queueName是一个随机的队列名称,可能看起来像:amq.gen-JzTY20BRgKO-HjmUJj0wLg

绑定

现在我们已经创建了一个fanout交换机和一个队列。现在我们需要告诉交换机给我们的队列发送消息。这种在交换机和队列之间的关系叫做绑定(binding)

channel.queueBind(queueName, "logs", "");

现在开始,logs交换机将向我们的队列追加消息。

Listing bingdings

使用rabbitmqctl list_bindings列出所有存在的绑定

Putting it all together

生产者程序,发送日志消息,看起来和之前的程序没有什么太大的区别。最重要的改变在我们现在希望发布消息到logs交换机,而不是之前的没有名字的交换机。在发送的时候,我们需要提供一个路由Key(routingKey),但是它的值被fanout交换机忽略了。下面是EmitLog.java:

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel; public class EmitLog { private static final String EXCHANGE_NAME = "logs"; public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); String message = getMessage(argv); channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'"); channel.close();
connection.close();
} private static String getMessage(String[] strings){
if (strings.length < 1)
return "info: Hello World!";
return joinStrings(strings, " ");
} private static String joinStrings(String[] strings, String delimiter) {
int length = strings.length;
if (length == 0) return "";
StringBuilder words = new StringBuilder(strings[0]);
for (int i = 1; i < length; i++) {
words.append(delimiter).append(strings[i]);
}
return words.toString();
}
}

如你所见,在创建了连接之后,我们声明了交换机。这一步是必须的,因为无法向一个不存在的交换机发布消息。

如果没有队列绑定到交换机上,消息会丢失,但对我们来说这没有什么,如果没有消费者监听我们可以安全的丢弃消息。

ReceiveLogs.java:

import com.rabbitmq.client.*;

import java.io.IOException;

public class ReceiveLogs {
private static final String EXCHANGE_NAME = "logs"; public static void main(String[] argv) throws Exception {
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();
channel.queueBind(queueName, EXCHANGE_NAME, ""); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
}
};
channel.basicConsume(queueName, true, consumer);
}
}

【RabbitMQ】Publish/Subscribe的更多相关文章

  1. 【RabbitMQ】显示耗时处理进度

    [RabbitMQ]显示耗时处理进度 通过网页提交一个耗时的请求,然后启动处理线程,请求返回.处理线程每完成一部分就给前台推送完成的数量,前端显示进度. 依赖jar <?xml version= ...

  2. 【RabbitMQ】 WorkQueues

    消息分发 在[RabbitMQ] HelloWorld中我们写了发送/接收消息的程序.这次我们将创建一个Work Queue用来在多个消费者之间分配耗时任务. Work Queues(又称为:Task ...

  3. 【rabbitmq】rabbitmq集群环境搭建

    安装rabbitmq-server 总共有3台虚拟机,都安装有rabbitmq服务,安装过程可参考: [rabbitmq]Centos7 下安装rabbitmq 创建用户和vhost 说明: 此步骤不 ...

  4. 【RabbitMQ】 Java简单的实现RabbitMQ

    准备工作 1.安装RabbitMQ,参考[RabbitMQ] RabbitMQ安装 2.新建Java项目,引入RabbitMQ的Maven依赖 <dependency> <group ...

  5. 【RabbitMQ】 RabbitMQ配置开机启动

    环境 系统:Linux(CentOS 7.2) Erlang环境:21.1(安装参考[Erlang]源码安装) RabbitMQ:3.7.9(安装参考[RabbitMQ] RabbitMQ安装) 配置 ...

  6. 【RabbitMQ】使用学习

    [RabbitMQ]使用学习 转载: ============================================================= =================== ...

  7. 【RabbitMQ】 RabbitMQ安装

    MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们.消息传递指的是程序之间 ...

  8. 【rabbitmq】rabbitmq概念解析--消息确认--示例程序

    概述 本示例程序全部来自rabbitmq官方示例程序,rabbitmq-demo: 官方共有6个demo,针对不同的语言(如 C#,Java,Spring-AMQP等),都有不同的示例程序: 本示例程 ...

  9. 【RabbitMQ】1、RabbitMQ的几种典型使用场景

    RabbitMQ主页:https://www.rabbitmq.com/ AMQP AMQP协议是一个高级抽象层消息通信协议,RabbitMQ是AMQP协议的实现.它主要包括以下组件: 1.Serve ...

随机推荐

  1. bzoj1023: [SHOI2008]cactus仙人掌图

    学习了一下圆方树. 圆方树是一种可以处理仙人掌的数据结构,具体见这里:http://immortalco.blog.uoj.ac/blog/1955 简单来讲它是这么做的:用tarjan找环,然后对每 ...

  2. python 小试题

    有个同事要帮一个朋友做一个小试题,题目如图: 由于个人在学习python路上,所以想用python 写出来这道题,来练练手,苦思冥想,再加上受同事的一些启发,加以扩展,写出代码如下: #!/usr/b ...

  3. 基于Z-WAVE 协议的LED智能照明系统的研究笔记

    LED调光基础: ☆:LED照明调光控制信号的方式有两种: 1. 通过PWM信号控制LED灯具开关电源的占空比从而实现调光: 2. 通过调光控制信号和交流电源供电线合用的两线式或三线式(例如LED相控 ...

  4. Java 集合类 TreeSet、TreeMap

    TreeMap和TreeSet的异同: 相同点: TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是拍好序的. TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之 ...

  5. 与你相遇好幸运,Sailsjs查询

    sailsjs 原生查询 ------------------------------------- Lands.native(function(err, collection) { if (err) ...

  6. Pyqt adb 获取Android手机屏幕

    adb的全称为Android Debug Bridge,就是起到调试桥的作用.adb的工作方式比较特殊,采用监听Socket TCP 5554等端口的方式让IDE和Qemu通讯,默认情况下adb会da ...

  7. WPF中Ribbon控件的使用

    这篇博客将分享如何在WPF程序中使用Ribbon控件.Ribbon可以很大的提高软件的便捷性. 上面截图使Outlook 2010的界面,在Home标签页中,将所属的Menu都平铺的布局,非常容易的可 ...

  8. CozyRSS开发记录2-酷炫的皮肤库

    CozyRSS开发记录2-酷炫的皮肤库 1.MaterialDesignToolkit 最开始微软推出Metro设计风格的时候,有人喜欢有人喷.紧接着,Ios也开始做扁平化的UI,这时候,扁平化已成为 ...

  9. linux中grep命令详解

    http://jingyan.baidu.com/article/76a7e409e72777fc3b6e158a.html

  10. Quick Apps for Sharepoint小型BI解决方案

    Quick Apps for Sharepoint介绍 Quick Apps for Sharepoint前身是Quest Webpart ,由企业软件开发商QuestSoftware开发,Quest ...