[30] RabbitMQ-工作模式
1. 工作模式#
1.1 Work Queue#
a. 说明#
生产者发消息,启动多个消费者实例来消费消息,每个消费者仅消费部分信息,可达到负载均衡的效果。
b. 案例#
生产者代码:
public class Producer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
// 声明一个消息队列
channel.queueDeclare("queue.wq", true, false, false, null);
// 声明Direct交换器
channel.exchangeDeclare("ex.wq", BuiltinExchangeType.DIRECT, true, false, null);
// 将消息队列绑定到指定的交换器,并指定绑定键
channel.queueBind("queue.wq", "ex.wq", "key.wq");
for (int i = 0; i < 15; i++) {
channel.basicPublish("ex.wq", "key.wq", null, ("Msg_" + i).getBytes("utf-8"));
}
channel.close();
connection.close();
}
}
消费者代码:
public class Consumer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare("queue.wq", true, false, false, null);
channel.basicConsume("queue.wq",
new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
System.out.println("Receive: " + new String(message.getBody(), "utf-8"));
}
},
new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
System.out.println("Cancel: " + consumerTag);
}
});
}
}
Consumer 跑 3 个,即 3 个消费者:
然后将生产者启动起来。
随即消费者控制台打印:
[Consuemr1]
Receive: Msg_0
Receive: Msg_3
Receive: Msg_6
Receive: Msg_9
Receive: Msg_12
[Consuemr2]
Receive: Msg_1
Receive: Msg_4
Receive: Msg_7
Receive: Msg_10
Receive: Msg_13
[Consuemr3]
Receive: Msg_2
Receive: Msg_5
Receive: Msg_8
Receive: Msg_11
Receive: Msg_14
1.2 发布订阅模式#
a. 说明#
使用 Fanout 类型交换器,routingKey 忽略。每个消费者定义生成一个队列并绑定到同一个 Exchange,每个消费者都可以消费到完整的消息。
消息广播给所有订阅该消息的消费者。
在 RabbitMQ 中,生产者不是将消息直接发送给消息队列,实际上生产者根本不知道一个消息被发送到哪个队列。
生产者将消息发送给交换器。交换器非常简单,从生产者接收消息,将消息推送给消息队列。交换器必须清楚地知道要怎么处理接收到的消息。应该是追加到一个指定的队列,还是追加到多个队列,还是丢弃。规则就是「交换器类型」。
交换器的类型前面已经介绍过了: direct 、 topic 、 headers 和 fanout 四种类型。发布订阅使用 Fanout。Fanout 交换器很简单,从名字就可以看出来(用风扇吹出去),将所有收到的消息发送给它知道的所有的队列。
列出 RabbitMQ 的交换器,包括了 amq.* 的和默认的(未命名)的交换器。
$ rabbitmqctl list_exchanges --formatter pretty_table
Listing exchanges for vhost / ...
+--------------------+---------+
| name | type |
+--------------------+---------+
| amq.direct | direct |
+--------------------+---------+
| amq.topic | topic |
+--------------------+---------+
| amq.match | headers |
+--------------------+---------+
| amq.rabbitmq.trace | topic |
+--------------------+---------+
| ex.biz | direct |
+--------------------+---------+
| amq.fanout | fanout |
+--------------------+---------+
| amq.headers | headers |
+--------------------+---------+
| ex.wq | direct |
+--------------------+---------+
| | direct |
+--------------------+---------+
未命名交换器
在前面的那里中我们没有指定交换器,但是依然可以向队列发送消息。这是因为我们使用了默认的交换器。
channel.basicPublish("", "hello", null, message.getBytes());
第一个参数就是交换器名称,为空字符串。直接使用 routingKey 向队列发送消息,会发送到名为 “hello“ 的队列(如果该队列存在)。
临时队列
前面我们使用队列的名称,生产者和消费者都是用该名称来发送和接收该队列中的消息。
首先,我们无论何时连接 RabbitMQ 的时候,都需要一个新的、空的队列。我们可以使用随机的名字创建队列,也可以让服务器帮我们生成随机的消息队列名字。
其次,一旦我们断开到消费者的连接,该队列应该自动删除。
String queueName = channel.queueDeclare().getQueue();
上述代码我们声明了一个非持久化的、排他的、自动删除的队列,并且名字是服务器随机生成的。
queueName 一般的格式类似:amq.gen-JzTY20BRgKO-HjmUJj0wLg 。
绑定
在创建了消息队列和 Fanout 类型的交换器之后,我们需要将两者进行绑定,让交换器将消息发送给该队列。
// Fanout 类型的交换器绑定不需要 BINDING_KEY
channel.queueBind(queueName, "logs", "");
此时, logs 交换器会将接收到的消息追加到 queueName 队列中。
b. 案例#
生产者代码:
public class Producer {
private static String FANOUT_EXCHANGE_NAME = "ex.my.fanout";
private static String FANOUT_BINDING_KEY = ""; // FANOUT 类型的交换器不需要指定路由键(绑定键)
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
// 声明 FANOUT 类型的交换器
channel.exchangeDeclare(FANOUT_EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true, false, null);
for (int i = 0; i < 10; i++) {
// FANOUT 类型的交换器不需要指定路由键
channel.basicPublish(FANOUT_EXCHANGE_NAME, FANOUT_BINDING_KEY, null, ("FANOUT TEST:" + i).getBytes("utf-8"));
}
channel.close();
connection.close();
}
}
消费者代码:
public class Consumer {
private static String FANOUT_EXCHANGE_NAME = "ex.my.fanout";
private static String FANOUT_BINDING_KEY = "";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
// 声明临时队列,队列的名字由RabbitMQ自动生成
final String queueName = channel.queueDeclare().getQueue();
System.out.println("生成的临时队列的名字为:" + queueName);
channel.exchangeDeclare(FANOUT_EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true, false, null);
// Fanout类型的交换器绑定不需要BingdingKey
channel.queueBind(queueName, FANOUT_EXCHANGE_NAME, FANOUT_BINDING_KEY);
channel.basicConsume(
queueName,
(consumerTag, message) -> System.out.println(String.format("Consumer[%s] receive: %s",
args[0], new String(message.getBody(), "utf-8"))),
consumerTag -> {}
);
}
}
生产者面向 Exchange,消费者面向 Queue。
先把消费者启动起来,然后通过命令 rabbitmqctl list_bindings
列出绑定关系:
[root@centos7 ~]# rabbitmqctl list_bindings --formatter pretty_table
Listing bindings for vhost /...
+--------------+-------------+--------------------------------+------------------+--------------------------------+-----------+
| source_name | source_kind | destination_name | destination_kind | routing_key | arguments |
+--------------+-------------+--------------------------------+------------------+--------------------------------+-----------+
| ex.my.fanout | exchange | amq.gen-LLTroI77YkR9MjOLYe93Fw | queue | | |
+--------------+-------------+--------------------------------+------------------+--------------------------------+-----------+
| ex.my.fanout | exchange | amq.gen-YKNXwWpmfZbMPLZlS_w1HQ | queue | | |
+--------------+-------------+--------------------------------+------------------+--------------------------------+-----------+
| ex.my.fanout | exchange | amq.gen-rEly-8lAd_oHzqkvZegTzQ | queue | | |
+--------------+-------------+--------------------------------+------------------+--------------------------------+-----------+
| ex.wq | exchange | queue.wq | queue | key.wq | |
+--------------+-------------+--------------------------------+------------------+--------------------------------+-----------+
消息的推拉
实现 RabbitMQ 的消费者有两种模式,推模式(Push)和拉模式(Pull)。
实现推模式推荐的方式是继承 DefaultConsumer 基类,也可以使用 Spring AMQP 的 SimpleMessageListenerContainer。推模式是最常用的,但是有些情况下推模式并不适用的,比如说: 由于某些限制,消费者在某个条件成立时才能消费消息,需要批量拉取消息进行处理。实现拉模式 —— RabbitMQ 的 Channel 提供了 basicGet 方法用于拉取消息。
1.3 路由模式-Direct#
a. 说明#
使用 Direct 类型的 Exchange,发 N 条消费并使用不同的 routingKey ,消费者定义队列并将 Queue、 RoutingKey 、Exchange 绑定。此时使用 Direct 模式Exchange 必须要 RoutingKey 完全匹配的情况下消息才会转发到对应的队列中被消费。
上一个模式中,可以将消息广播到很多接收者。
// [Fanout] 交换机的使用方式,第三个参数 BingdingKey 不使用
channel.queueBind(queueName, EXCHANGE_NAME, "");
BindingKey 的作用与具体使用的交换器类型有关。对于 Fanout 类型的交换器,此参数设置无效,系统直接忽略。
场景如下:分布式系统中有很多应用,这些应用需要运维平台的监控,其中一个重要的信息就是服务器的日志记录。我们需要将不同日志级别的日志记录交给不同的应用处理。
如何解决?
使用 Direct 交换器。
如果要对不同的消息做不同的处理,此时不能使用 Fanout 类型的交换器,因为它只会盲目的广播消息。我们需要使用 Direct 类型的交换器。 Direct 交换器的路由算法很简单:只要消息的 RoutingKey 和队列的 BindingKey 对应,消息就可以推送给该队列。
上图中的交换器 X 是 Direct 类型的交换器,绑定的两个队列中,一个队列的 BindingKey 是 orange ,另一个队列的 BindingKey 是 black 和 green。如此,则 RoutingKey 是 orange 的消息发送给队列 Q1, RoutingKey 是 black 和 green 的消息发送给 Q2 队列,其他消息丢弃。
【多重绑定】
如图,虽然 Exchange 的绑定类型是 Direct,但是它绑定的多个队列的 BindingKey 如果都相同,在这种情况下虽然绑定类型是 Direct 但是它表现的就和 Fanout有点类似了,就跟广播差不多。
b. 案例#
在案例中,我们将日志级别作为 RoutingKey。
(1)ReceiveLogsDirect01
public class ReceiveLogsDirect01 {
private static final String EXCHANGE_NAME = "direct_logs";
private static final String QUEUE_NAME = "disk";
private static final String BINDING_KEY = "error";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY);
System.out.println("等待接收消息...");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("ERROR>>> " + message);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
}
}
(2)ReceiveLogsDirect02
public class ReceiveLogsDirect02 {
private static final String EXCHANGE_NAME = "direct_logs";
private static final String QUEUE_NAME = "console";
private static final String BINDING_KEY_INFO = "info";
private static final String BINDING_KEY_WARNING = "warning";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY_INFO);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY_WARNING);
System.out.println("等待接收消息...");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(delivery.getEnvelope().getRoutingKey() + "> " + message);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
}
}
(3)EmitLogDirect
public class EmitLogDirect {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 创建多个BindingKey(绑定是'交换机'和'队列'之间的桥梁关系)
Map<String, String> bindingKeyMap = new HashMap<>();
bindingKeyMap.put("info", "普通 info 信息");
bindingKeyMap.put("warning", "警告 warning 信息");
bindingKeyMap.put("error", "错误 error 信息");
bindingKeyMap.put("debug", "调试 debug 信息"); // 该消息默认会被丢弃
for (Map.Entry<String, String> bindingKeyEntry : bindingKeyMap.entrySet()) {
String bindingKey = bindingKeyEntry.getKey();
String message = bindingKeyEntry.getValue();
channel.basicPublish(EXCHANGE_NAME, bindingKey, null, message.getBytes("UTF-8"));
System.out.println("SendMsg: " + message);
}
}
}
多个消费者消费的消息量加起来才是生产者发出的消息量。
1.4 路由模式-Topic#
a. 说明#
使用 topic 类型的交换器,Queue 绑定到 Exchange、 BindingKey 时使用通配符,交换器将消息路由转发到具体队列时会根据消息 RoutingKey 模糊匹配,比较灵活。
上个模式中,我们通过 Direct 类型的交换器做到了根据日志级别的不同,将消息发送给了不同队列的。
这里有一个限制,加入现在我不仅想根据日志级别划分日志消息,还想根据日志来源划分日志,怎么做?
比如,我想监听 cron 服务发送的 error 消息,又想监听从 kern 服务发送的所有消息。
此时可以使用 RabbitMQ 的 Topic 模式。
要想 topic 类型的交换器, RoutingKey 就不能随便写了,它必须得是点分单词。
单词可以随便写,生产中一般使用消息的特征。如:“stock.usd.nyse”,“nyse.vmw”,“quick.orange.rabbit”等。该点分单词字符串最长 255 字节。
BindingKey 也必须是这种形式。 topic 类型的交换器背后原理跟 Direct 类型的类似:只要队列的 BindingKey 的值与消息的 RoutingKey 匹配,队列就可以收到该消息。但有两个不同:
*
匹配 1 个单词#
匹配 0 到多个单词
【举例说明】我们发送描述动物的消息。消息发送的时候指定的 RoutingKey 包含了三个词,两个点。第一个单词表示动物的速度,第二个是颜色,第三个是物种:<speed>.<color>.<species>
。
绑定关系如下:
Q1 绑定的是:
* 中间带 orange 带 3 个单词的字符串(*.orange.*)
Q2 绑定的是:
* 最后一个单词是 rabbit 的 3 个单词(*.*.rabbit)
* 第一个单词是 lazy 的多个单词(lazy.#)
上图是一个队列绑定关系图,我们来看看他们之间数据接收情况是怎么样的:
quick.orange.rabbit 被队列 Q1Q2 接收到
lazy.orange.elephant 被队列 Q1Q2 接收到
quick.orange.fox 被队列 Q1 接收到
lazy.brown.fox 被队列 Q2 接收到
lazy.pink.rabbit 虽然满足两个绑定但只被队列 Q2 接收一次
quick.brown.fox 不匹配任何绑定不会被任何队列接收到会被丢弃
quick.orange.male.rabbit 是四个单词不匹配任何绑定会被丢弃
lazy.orange.male.rabbit 是四个单词但匹配 Q2
- 如果在 topic 类型的交换器中 BindingKey 使用
#
,则就是 Fanout 类型交换器的行为。 - 如果在 topic 类型的交换器中 BindingKey 中不使用
*
和#
,则就是 Direct 类型交换器的行为。
b. 案例#
(1)消费者1
public class ReceiveLogsTopic01 {
private static final String EXCHANGE_NAME = "topic_logs";
private static final String QUEUE_NAME = "Q1";
private static final String BINDING_KEY = "*.orange.*";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 声明 Q1 队列与绑定关系
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY);
System.out.println("等待接收消息...");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("接收队列:" + QUEUE_NAME + ",绑定键:" + delivery.getEnvelope().getRoutingKey() + ",消息:" + message);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
}
}
(2)消费者2
public class ReceiveLogsTopic02 {
private static final String EXCHANGE_NAME = "topic_logs";
private static final String QUEUE_NAME = "Q2";
private static final String BINDING_KEY_1 = "*.*.rabbit";
private static final String BINDING_KEY_2 = "lazy.#";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 声明 Q2 队列与绑定关系
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY_1);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY_2);
System.out.println("等待接收消息...");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("接收队列:" + QUEUE_NAME + ",绑定键:" + delivery.getEnvelope().getRoutingKey() + ",消息:" + message);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
}
}
(3)生产者
public class EmitLogTopic {
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://root:123456@192.168.6.160:5672/%2f");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
/**
* Q1 -> 绑定的是:
* - 中间带 orange 带 3 个单词的字符串(*.orange.*)
* Q2 -> 绑定的是
* - 最后一个单词是 rabbit 的 3 个单词(*.*.rabbit)
* - 第一个单词是 lazy 的多个单词(lazy.#)
*/
Map<String, String> bindingKeyMap = new HashMap<>();
bindingKeyMap.put("quick.orange.rabbit", "被队列 Q1Q2 接收到");
bindingKeyMap.put("lazy.orange.elephant", "被队列 Q1Q2 接收到");
bindingKeyMap.put("quick.orange.fox", "被队列 Q1 接收到");
bindingKeyMap.put("lazy.brown.fox", "被队列 Q2 接收到");
bindingKeyMap.put("lazy.pink.rabbit", "虽然满足两个绑定但只被队列 Q2 接收一次");
bindingKeyMap.put("quick.brown.fox", "不匹配任何绑定不会被任何队列接收到会被丢弃");
bindingKeyMap.put("quick.orange.male.rabbit", "是四个单词不匹配任何绑定会被丢弃");
bindingKeyMap.put("lazy.orange.male.rabbit", "是四个单词但匹配 Q2");
for (Map.Entry<String, String> bindingKeyEntry : bindingKeyMap.entrySet()) {
String bindingKey = bindingKeyEntry.getKey();
String message = bindingKeyEntry.getValue();
channel.basicPublish(EXCHANGE_NAME, bindingKey, null, message.getBytes("UTF-8"));
System.out.println("生产者发出消息:" + message);
}
}
}
2. 整合 Spring#
spring-amqp 是对 AMQP 的一些概念的一些抽象,spring-rabbit 是对 RabbitMQ 操作的封装实现。
主要有几个核心类 RabbitAdmin 、 RabbitTemplate 、 SimpleMessageListenerContainer 等。
- RabbitAdmin 类完成对 Exchange、Queue、Binding 的操作,在容器中管理了 RabbitAdmin 类的时候,可以对 Exchange,Queue,Binding 进行自动声明。
- RabbitTemplate 类是发送和接收消息的工具类。
- SimpleMessageListenerContainer 是消费消息的容器。
目前比较新的一些项目都会选择基于注解方式,而比较老的一些项目可能还是基于配置文件的。
2.1 基于配置文件整合#
(1)创建 Maven 工程
(2)配置 pom.xml,添加 RabbitMQ 的 Spring 依赖
<dependencies>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
</dependencies>
(3)rabbit-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<rabbit:connection-factory id="connectionFactory"
host="node1"
virtual-host="/"
username="root"
password="123456"
port="5672"/>
<!-- 创建一个rabbit的template对象(org.springframework.amqp.rabbit.core.RabbitTemplate)以便于访问broker -->
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
<!-- 自动查找类型是Queue、Exchange、Binding的bean,并为用户向 RabbitMQ 声明 -->
<!-- 因此,我们不需要显式地在java中声明 -->
<rabbit:admin id="rabbitAdmin" connection-factory="connectionFactory"/>
<!-- 为消费者创建一个队列,如果broker中存在,则使用同名存在的队列,否则创建一个新的。 -->
<!-- 如果要发送消息,得使用交换器 -->
<!-- 这里使用的是默认的交换器 -->
<rabbit:queue name="myqueue"/>
<rabbit:direct-exchange name="direct.biz.ex" auto-declare="true" auto-delete="false" durable="false">
<rabbit:bindings>
<!-- exchange:其他绑定到该交换器的交换器名称 -->
<!-- queue:绑定到该交换器的queue的bean名称 -->
<!-- bindingKey:显式声明的绑定key -->
<rabbit:binding queue="myqueue" key="dir.ex"/>
</rabbit:bindings>
</rabbit:direct-exchange>
</beans>
(4)Application.java
/**
* 使用 Spring XML 配置的方式发送接接收消息
*/
public class App {
public static void main(String[] args) {
AbstractApplicationContext context = new GenericXmlApplicationContext("classpath:/rabbit-context.xml");
AmqpTemplate template = context.getBean(AmqpTemplate.class);
for (int i = 0; i < 1000; i++) {
// 第一个参数是路由key,第二个参数是消息
template.convertAndSend("dir.ex", "foo" + i);
}
// 主动从队列拉取消息
String foo = (String) template.receiveAndConvert("myqueue");
System.out.println(foo);
context.close();
}
}
启动 RabbitMQ 之后,直接运行即可。
2.2 基于注解整合#
(1)创建 Maven 项目
(2)配置 pom.xml,添加 RabbitMQ 的 Spring 依赖
(3)添加 ProducerApp 配置类 RabbitProducerConfig.java
@Configuration
public class RabbitProducerConfig {
// 连接工厂
@Bean
public ConnectionFactory connectionFactory() {
ConnectionFactory factory = new CachingConnectionFactory(URI.create("amqp://root:123456@node1:5672/%2f"));
return factory;
}
// RabbitTemplate
@Bean
@Autowired
public RabbitTemplate rabbitTemplate(ConnectionFactory factory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);
return rabbitTemplate;
}
// RabbitAdmin
@Bean
@Autowired
public RabbitAdmin rabbitAdmin(ConnectionFactory factory) {
RabbitAdmin rabbitAdmin = new RabbitAdmin(factory);
return rabbitAdmin;
}
// Queue
@Bean
public Queue queue() {
final Queue queue = QueueBuilder.nonDurable("queue.anno").build();
return queue;
}
// Exchange
@Bean
public Exchange exchange() {
final FanoutExchange fanoutExchange = new FanoutExchange("ex.anno.fanout", false, false, null);
return fanoutExchange;
}
// Binding
@Bean
@Autowired
public Binding binding(Queue queue, Exchange exchange) {
// 创建一个绑定,不指定绑定的参数
final Binding binding = BindingBuilder.bind(queue).to(exchange).with("key.anno").noargs();
return binding;
}
}
(4)ProducerApp 启动类
public class ProducerApp {
public static void main(String[] args) throws UnsupportedEncodingException {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(RabbitProducerConfig.class);
final RabbitTemplate template = context.getBean(RabbitTemplate.class);
final MessageProperties messageProperties = MessagePropertiesBuilder
.newInstance()
.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
.setContentEncoding("gbk")
.setHeader("myKey", "myValue")
.build();
final Message message = MessageBuilder
.withBody(("HelloWorld").getBytes("gbk"))
.andProperties(messageProperties)
.build();
template.send("ex.anno.fanout", "key.anno", message);
context.close();
}
}
(5)另建一个 Maven 项目,添加 ConsumerApp 配置类 RabbitConsumerConfig.java
@Configuration
public class RabbitConsumerConfig {
@Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory(URI.create("amqp://root:123456@node1:5672/%2f"));
}
@Bean
@Autowired
public RabbitTemplate rabbitTemplate(ConnectionFactory factory) {
return new RabbitTemplate(factory);
}
@Bean
@Autowired
public RabbitAdmin rabbitAdmin(ConnectionFactory factory) {
return new RabbitAdmin(factory);
}
@Bean
public Queue queue() {
return QueueBuilder.nonDurable("queue.anno").build();
}
}
(6)ConsumerApp 启动类
public class ConsumerApp {
public static void main(String[] args) throws UnsupportedEncodingException {
// 从指定类加载配置信息
AbstractApplicationContext context = new AnnotationConfigApplicationContext(RabbitConsumerConfig.class);
// 获取RabbitTemplate对象
final RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
// 接收消息
final Message message = rabbitTemplate.receive("queue.anno");
// 打印消息
System.out.println(new String(message.getBody(), message.getMessageProperties().getContentEncoding()));
// 关闭Spring的上下文
context.close();
}
}
3. 整合 SpringBoot#
(1)添加 starter 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
(2)application.properties 中添加连接信息
spring.application.name=springboot_rabbitmq
spring.rabbitmq.host=node1
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=root
spring.rabbitmq.password=123456
spring.rabbitmq.port=5672
(3)RabbitConfig 类
@Configuration
public class RabbitConfig {
@Bean
public Queue queue() {
return new Queue("queue.boot", false, false, false, null);
}
@Bean
public Exchange exchange() {
return new TopicExchange("ex.boot", false, false, null);
}
@Bean
public Binding binding() {
return new Binding("queue.boot", Binding.DestinationType.QUEUE, "ex.boot", "key.boot", null);
}
}
(4)使用 RestController 发送消息
@RestController
public class MessageController {
@Autowired
private AmqpTemplate rabbitTemplate;
@RequestMapping("/rabbit/{message}")
public String receive(@PathVariable String message) throws UnsupportedEncodingException {
final MessageProperties messageProperties = MessagePropertiesBuilder.newInstance()
.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
.setContentEncoding("utf-8")
.setHeader("hello", "world")
.build();
final Message msg = MessageBuilder
.withBody(message.getBytes("utf-8"))
.andProperties(messageProperties)
.build();
rabbitTemplate.send("ex.boot", "key.boot", msg);
return "ok";
}
}
(5)使用监听器,用于消费消息
@Component
public class MyMessageListener {
// @RabbitListener(queues = "queue.boot")
// public void getMyMessage(@Payload String message, @Header(name = "hello") String value, Channel channel) {
// System.out.println(message);
// System.out.println("hello = " + value);
//
// // 确认消息
// channel.basicAck();
// // 拒收消息
// channel.basicReject();
// }
private Integer index = 0;
@RabbitListener(queues = "queue.boot")
public void getMyMessage(Message message, Channel channel) throws IOException {
String value = message.getMessageProperties().getHeader("hello");
System.out.println(message);
System.out.println("hello = " + value);
final long deliveryTag = message.getMessageProperties().getDeliveryTag();
if (index % 2 == 0) {
// 确认消息
channel.basicAck(deliveryTag, false);
} else {
// 拒收消息
channel.basicReject(deliveryTag, false);
}
index++;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?