【RabbitMQ】Publish/Subscribe
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的更多相关文章
- 【RabbitMQ】显示耗时处理进度
[RabbitMQ]显示耗时处理进度 通过网页提交一个耗时的请求,然后启动处理线程,请求返回.处理线程每完成一部分就给前台推送完成的数量,前端显示进度. 依赖jar <?xml version= ...
- 【RabbitMQ】 WorkQueues
消息分发 在[RabbitMQ] HelloWorld中我们写了发送/接收消息的程序.这次我们将创建一个Work Queue用来在多个消费者之间分配耗时任务. Work Queues(又称为:Task ...
- 【rabbitmq】rabbitmq集群环境搭建
安装rabbitmq-server 总共有3台虚拟机,都安装有rabbitmq服务,安装过程可参考: [rabbitmq]Centos7 下安装rabbitmq 创建用户和vhost 说明: 此步骤不 ...
- 【RabbitMQ】 Java简单的实现RabbitMQ
准备工作 1.安装RabbitMQ,参考[RabbitMQ] RabbitMQ安装 2.新建Java项目,引入RabbitMQ的Maven依赖 <dependency> <group ...
- 【RabbitMQ】 RabbitMQ配置开机启动
环境 系统:Linux(CentOS 7.2) Erlang环境:21.1(安装参考[Erlang]源码安装) RabbitMQ:3.7.9(安装参考[RabbitMQ] RabbitMQ安装) 配置 ...
- 【RabbitMQ】使用学习
[RabbitMQ]使用学习 转载: ============================================================= =================== ...
- 【RabbitMQ】 RabbitMQ安装
MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们.消息传递指的是程序之间 ...
- 【rabbitmq】rabbitmq概念解析--消息确认--示例程序
概述 本示例程序全部来自rabbitmq官方示例程序,rabbitmq-demo: 官方共有6个demo,针对不同的语言(如 C#,Java,Spring-AMQP等),都有不同的示例程序: 本示例程 ...
- 【RabbitMQ】1、RabbitMQ的几种典型使用场景
RabbitMQ主页:https://www.rabbitmq.com/ AMQP AMQP协议是一个高级抽象层消息通信协议,RabbitMQ是AMQP协议的实现.它主要包括以下组件: 1.Serve ...
随机推荐
- mpp文件转换成jpg图片,可以用pdf文件做中转站
用project软件做了一个表,发现不能转换成图片,先把mpp文件转换成pdf文件,然后用PS打开pdf文件,存储为jpg格式就行了
- 获取FIle路径下所有文件的地址和名称
public static void getFileName(File[] files) { String address=""; if (files != null)// 先判断 ...
- 图片切换小demo
<body> <div class="body"><img src="bopin/images/bigImg1.jpg" widt ...
- 使用 PowerDesigner 和 PDMReader 逆向生成 MySQL 数据字典
下面提到的软件大家可以在下面的链接下载. 大家可以参考下面的操作录制视频来完成相关的操作. 使用 PowerDesigner 和 PDMReader 逆向生成 MySQL 数据字典.wmv_免费高速下 ...
- Oracle 11g RAC 卸载CRS步骤
Oracle 11g之后提供了卸载grid和database的脚本,可以卸载的比较干净,不需要手动删除crs ##########如果要卸载RAC,需要先使用dbca删除数据库,在执行下面的操作### ...
- WinForm 与WPF 窗体之间的想到调用
先放置一个容器控件,并设计 好WinForm(或WPF)窗口 winform 调用 wpf ElementHost el = new ElementHost(); el.Dock = DockStyl ...
- [perl]字符串转拼音首字母(支持多音字)
实现的思路是,查表找到该字的所有读音,然后取首字母. 代码: while (<DATA>) { chomp; })(.*)$/; $all =~ s/^\s+//; ### 只保留无音标号 ...
- 关闭英文拼写检查,关闭xml验证
http://blog.sina.com.cn/s/blog_70b623e4010173ce.html eclipse里面的许多设置对于国内开发者日常使用不太适用,反而会成为干扰.既然是完全可配置的 ...
- tomcat 配置项目指定域名
<Host name="www.xxx.com" appBase="D:/tomcat/webapps/web"> <alias>xxx ...
- DNS域名解析过程
图1-10是DNS域名解析的主要请求过程实例图. 如图1-10所示,当一个用户在浏览器中输入www.abc.com时,DNS解析将会有将近10个步骤,这个过程大体描述如下.当用户在浏览器中输入域名并按 ...