译: 3. RabbitMQ Spring AMQP 之 Publish/Subscribe 发布和订阅
在第一篇教程中,我们展示了如何使用start.spring.io来利用Spring Initializr创建一个具有RabbitMQ starter dependency的项目来创建spring-amqp应用程序。
在上一个教程中,我们创建了一个新的包(tut2)来放置我们的配置,发送者和接收者,并创建了一个包含两个使用者的工作队列。工作队列背后的假设是每个任务都交付给一个工作者。
在这部分中,我们将实现扇出模式,以向多个消费者传递消息。此模式称为 Publish/Subscribe “发布/订阅”,并通过在Tut3Config文件中配置多个bean来实现。
基本上,已发布的消息将被广播给所有接收者。
Exchanges
在本教程的前几部分中,我们向队列发送消息和从队列接收消息。现在是时候在Rabbit中引入完整的消息传递模型了。
让我们快速回顾一下前面教程中介绍的内容:
- 甲生产者是发送消息的用户的应用程序。
- 甲队列是存储消息的缓冲器。
- 甲消费者是接收消息的用户的应用程序。
RabbitMQ中消息传递模型的核心思想是生产者永远不会将任何消息直接发送到队列。实际上,生产者通常甚至不知道消息是否会被传递到任何队列。
相反,生产者只能向交易所发送消息。Exchanges交换是一件非常简单的事情。一方面,它接收来自生产者的消息,另一方面将它们推送到队列。交易所必须确切知道如何处理收到的消息。它应该附加到特定队列吗?它应该附加到许多队列吗?或者它应该被丢弃。其规则由交换类型定义 。

有几种交换类型可供选择:
- direct
- topic
- headers
- fanout
我们将专注于最后一个 - fanout。让我们配置一个bean来描述这种类型的交换,并将其命名为tut.fanout:
Tut3Config.java
import org.springframework.amqp.core.AnonymousQueue;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile; import com.xingyun.springamqp.business.Tut3Receiver;
import com.xingyun.springamqp.business.Tut3Sender; @Profile({ "tut3", "pub-sub", "publish-subscribe" })
@Configuration
public class Tut3Config {
@Bean
public FanoutExchange fanout() {
return new FanoutExchange("tut.fanout");
} @Profile("receiver")
private static class ReceiverConfig { @Bean
public Queue autoDeleteQueue1() {
return new AnonymousQueue();
} @Bean
public Queue autoDeleteQueue2() {
return new AnonymousQueue();
} @Bean
public Binding binding1(FanoutExchange fanout, Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(fanout);
} @Bean
public Binding binding2(FanoutExchange fanout, Queue autoDeleteQueue2) {
return BindingBuilder.bind(autoDeleteQueue2).to(fanout);
} @Bean
public Tut3Receiver receiver() {
return new Tut3Receiver();
}
} @Profile("sender")
@Bean
public Tut3Sender sender() {
return new Tut3Sender();
}
}
我们遵循与前两个教程相同的方法。我们创建了三个配置文件,即教程(“tut3”,“pub-sub”或“publish-subscribe”)。它们都是运行fanout 配置文件教程的同义词。
接下来,我们将FanoutExchange配置为bean。
在“接收器”(Tut3Receiver)文件中,我们定义“四个bean;
- 两个autoDeleteQueues或AnonymousQueues
- 以及两个绑定来将这些队列绑定到交换机。
fanout交换非常简单。正如您可能从名称中猜到的那样,它只是将收到的所有消息广播到它知道的所有队列中。而这正是我们传播信息所需要的。
列出交换
要列出服务器上的交换,您可以运行有用的rabbitmqctl:
sudo rabbitmqctl list_exchanges在此列表中将有一些amq。*交换和默认(未命名)交换。这些是默认创建的,但目前您不太可能需要使用它们。
Nameless exchange 无名交换
在本教程的前几部分中,我们对交换一无所知,但仍能够向队列发送消息。这是可能的,因为我们使用的是默认交换,我们通过空字符串(“”)来识别。
回想一下我们之前是如何发布消息的:
template.convertAndSend(fanout.getName(),“”,message);第一个参数是自动装入发件人的交换的名称。空字符串表示默认或无名交换:消息被路由到具有routingKey指定名称的队列(如果存在)。
现在,我们可以发布到我们的命名交换:
@Autowired
private RabbitTemplate template; @Autowired
private FanoutExchange fanout; // configured in Tut3Config above template.convertAndSend(fanout.getName(), "", message);
从现在开始,fanout交换会将消息附加到我们的队列中。
临时队列
您可能还记得以前我们使用过具有特定名称的队列(记住你好)。能够命名队列对我们来说至关重要 - 我们需要将工作人员指向同一个队列。
当您想要在生产者和消费者之间共享队列时,为队列命名很重要。但我们的粉丝示例并非如此。
我们希望了解所有消息,而不仅仅是它们的一部分。我们也只对目前流动的消息感兴趣,而不是旧消息。要解决这个问题,我们需要两件事。
首先,每当我们连接到Rabbit时,我们都需要一个新的空队列。为此,我们可以使用随机名称创建队列,或者更好 - 让服务器为我们选择随机队列名称。
其次,一旦我们断开消费者,就应该自动删除队列。为了使用spring-amqp客户端,我们定义了AnonymousQueue,它创建了一个带有生成名称的非持久的独占自动删除队列:
@Bean
public Queue autoDeleteQueue1() {
return new AnonymousQueue();
} @Bean
public Queue autoDeleteQueue2() {
return new AnonymousQueue();
}
此时,我们的队列名称包含随机队列名称。例如,它可能看起来像amq.gen-JzTY20BRgKO-HjmUJj0wLg
绑定

我们已经创建了一个扇出交换和一个队列。现在我们需要告诉交换机将消息发送到我们的队列。交换和队列之间的关系称为绑定。
在上面的Tut3Config中,您可以看到我们有两个绑定,每个AnonymousQueue一个。
@Bean
public Binding binding1(FanoutExchange fanout,
Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(fanout);
}
列出绑定
您可以使用,您猜对了,列出现有绑定
rabbitmqctl list_bindings
把它们放在一起

发出消息的生产者程序与前一个教程没有太大的不同。
最重要的变化是我们现在想要将消息发布到我们的扇出交换而不是无名交换。
我们需要在发送时提供routingKey,但是对于扇出交换,它的值会被忽略。这里是tut3.Sender.java程序的代码 :
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled; public class Tut3Sender { @Autowired
private RabbitTemplate template; @Autowired
private FanoutExchange fanout; int dots = 0; int count = 0; @Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() {
StringBuilder builder = new StringBuilder("Hello");
if (dots++ == 3) {
dots = 1;
}
for (int i = 0; i < dots; i++) {
builder.append('.');
}
builder.append(Integer.toString(++count));
String message = builder.toString();
template.convertAndSend(fanout.getName(), "", message);
System.out.println(" [x] Sent '" + message + "'");
}
}
如您所见,我们利用Tut3Config文件中的bean以及RabbitTemplate中的自动装配以及我们配置的FanoutExchange这一步是必要的,因为禁止发布到不存在的交换。
如果没有队列绑定到交换机,消息将会丢失,但这对我们没有问题; 如果没有消费者在听,我们可以安全地丢弃该消息。
消费者
Tut3Receiver.java
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.util.StopWatch; public class Tut3Receiver { @RabbitListener(queues = "#{autoDeleteQueue1.name}") public void receive1(String in) throws InterruptedException {
receive(in, 1);
} @RabbitListener(queues = "#{autoDeleteQueue2.name}")
public void receive2(String in) throws InterruptedException {
receive(in, 2);
} public void receive(String in, int receiver) throws InterruptedException {
StopWatch watch = new StopWatch();
watch.start();
System.out.println("instance " + receiver + " [x] Received '" + in + "'");
doWork(in);
watch.stop();
System.out.println("instance " + receiver + " [x] Done in " + watch.getTotalTimeSeconds() + "s");
} private void doWork(String in) throws InterruptedException {
for (char ch : in.toCharArray()) {
if (ch == '.') {
Thread.sleep(1000);
}
}
}
}
查看用法
java -jar RabbitMQ_0x03_SpringAMQP_PublishSubscribe_Sample-0.0.1-SNAPSHOT.jar

这次和之前有所不同,这次消费者和生产者必须同时运行才得行。
消费者和生产者等待时间都是60秒
启动消费者
java -jar RabbitMQ_0x03_SpringAMQP_PublishSubscribe_Sample-0.0.1-SNAPSHOT.jar --spring.profiles.active=pub-sub,receiver
显示效果如下:

启动生产者
java -jar RabbitMQ_0x03_SpringAMQP_PublishSubscribe_Sample-0.0.1-SNAPSHOT.jar --spring.profiles.active=pub-sub,sender
显示效果如下:

译: 3. RabbitMQ Spring AMQP 之 Publish/Subscribe 发布和订阅的更多相关文章
- 译: 1. RabbitMQ Spring AMQP 之 Hello World
本文是译文,原文请访问:http://www.rabbitmq.com/tutorials/tutorial-one-spring-amqp.html RabbitMQ 是一个Brocker (消息队 ...
- 译:3.RabbitMQ Java Client 之 Publish/Subscribe(发布和订阅)
在上篇 RabbitMQ 之Work Queues (工作队列)教程中,我们创建了一个工作队列,工作队列背后的假设是每个任务都交付给一个工作者. 在这一部分,我们将做一些完全不同的事情 - 我们将向多 ...
- 译: 2. RabbitMQ Spring AMQP 之 Work Queues
在上一篇博文中,我们写了程序来发送和接受消息从一个队列中. 在这篇博文中我们将创建一个工作队列,用于在多个工作人员之间分配耗时的任务. Work Queues 工作队列(又称:任务队列)背后的主要思想 ...
- 译: 6. RabbitMQ Spring AMQP 之 RPC
Remote procedure call (RPC) 在第二篇教程中,我们学习了如何使用工作队列在多个工作人员之间分配耗时的任务. 但是如果我们需要在远程计算机上运行一个函数并等待结果呢?嗯,这是一 ...
- 译: 4. RabbitMQ Spring AMQP 之 Routing 路由
在上一个教程中,我们构建了一个简单的fanout(扇出)交换.我们能够向许多接收者广播消息. 在本教程中,我们将为其添加一个功能 - 我们将只能订阅一部分消息.例如,我们将只能将消息指向感兴趣的特定颜 ...
- 译: 5. RabbitMQ Spring AMQP 之 Topic 主题
在上一个教程中,我们提高了消息传递的灵活 我们使用direct交换而不是使用仅能够进行虚拟广播的fanout交换, 并且获得了基于路由key 有选择地接收消息的可能性. 虽然使用direct 交换改进 ...
- RabbitMQ官方教程三 Publish/Subscribe(GOLANG语言实现)
RabbitMQ官方教程三 Publish/Subscribe(GOLANG语言实现) 在上一个教程中,我们创建了一个工作队列. 工作队列背后的假设是,每个任务都恰好交付给一个worker处理. 在这 ...
- RabbitMQ(三) -- Publish/Subscribe
RabbitMQ(三) -- Publish/Subscribe `rabbitmq`支持一对多的模式,一般称为发布/订阅.也就是说,生产者产生一条消息后,`rabbitmq`会把该消息分发给所有的消 ...
- php redis pub/sub(Publish/Subscribe,发布/订阅的信息系统)之基本使用
一.场景介绍 最近的一个项目需要用到发布/订阅的信息系统,以做到最新实时消息的通知.经查找后发现了redis pub/sub(发布/订阅的信息系统)可以满足我的开发需求,而且学习成本和使用成本也比较低 ...
随机推荐
- BZOJ1088 [SCOI2005]扫雷Mine 动态规划
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ1088 题意概括 扫雷.只有2行.第2行没有雷,第一行有雷.告诉你第二行显示的数组,问有几种摆放方式 ...
- 【noip模拟赛6】收入计划 最大值的最小值 二分答案
描述 高考结束后,同学们大都找到了一份临时工作,渴望挣得一些零用钱.从今天起,Matrix67将连续工作N天(1<=N<=100 000).每一天末他可以领取当天及前面若干天里没有领取的工 ...
- Python2 - 基础2 - 数据类型和模块
一.数据类型 标准数据类型(5): Numbers(数字) String(字符串) List(列表) Tuple(元组) Dictionary(字典) 其中数字类型有4种: int(有符号整型) 在3 ...
- RelativeLayout的16种特有属性
*相对于兄弟控件的位置属性 android:layout_above="@id/center_btn"处于某一个控件的上方 android:layout_below="@ ...
- Jmeter的安装和启动错误总结,出现unable to access jarfile apachejmeter.jar error value=1错误处理
Jmeter是纯Java开发的, 能够运行Java程序的系统一般都可以运行Jmeter, 如:Windows. Linux. mac等. 由于是由Java开发,所以自然需要jdk环境. Windows ...
- js异步刷新局部页面
真不想说博客园的Markdown编辑器,我发表到我的个人博客上多好看的一篇文章,发到博客园上格式就成这个鸟样了,哎,不发现到博客首页了,就个人存个档吧 最近在做一个异步刷新页面中的局部,这样做可以防出 ...
- BZOJ 3994: [SDOI2015]约数个数和3994: [SDOI2015]约数个数和 莫比乌斯反演
https://www.lydsy.com/JudgeOnline/problem.php?id=3994 https://blog.csdn.net/qq_36808030/article/deta ...
- Gym - 100548G The Problem to Slow Down You
依然是回文树. 我们只需要吧siz[]改成统计两边的siz[][0/1],然后把两个字符中间随便加一个不会出现的字符拼起来,做一遍回文树统计一下就OJBK了 #include<bits/stdc ...
- 潭州课堂25班:Ph201805201 爬虫基础 第二课 fidder (课堂笔记)
通过浏览器访问百度的详细过程? 一.通过dns获取百度IP地址.二.通过百度IP访问百度服务器, 三,返回数据. 四,通过渲染显示内容, fidder设置 tf 信任证书
- JVM的Client模式与Server模式
概述 JVM有两种运行模式Server与Client.两种模式的区别在于,Client模式启动速度较快,Server模式启动较慢:但是启动进入稳定期长期运行之后Server模式的程序运行速度比Clie ...