rabitmq官方网站:RabbitMQ Tutorials — RabbitMQ
一.基本概念
基于AMPQ协议:高级消息队列协议 Advanced Message Queue Protocol
生产者(Publisher):发消息到某个交换机
消费者(Consumer):从某个队列取消息
交换机(Exchange):负责把消息转发到对应的队列
队列(Queue):存储消息
路由(Routes):转发,就是怎么把消息从一个地方转发到另一个地方
二.基本准备与操作
①安装erlang并配置环境变量,因为rabbitmq依赖erlang
25.3.2 - Erlang (及其erlang的其他版本)
②安装rabbitmq Installing on Windows — RabbitMQ
③安装二者后,检查rabbitmq是否启动
command + r 打开 services.msc (服务菜单)
---若安装rabbitmq出现如下图情况,检查erlang版本是否和rabbitmq版本对应
我的安装版本
erlang v-26.1 rabbitmq v-3.12.5
④安装rabbitmq监控面板
在rabbitmq的安装目录sbin下执行如下脚本
rabbitmq-plugins.bat enable rabbitmq_ management
初始账户密码都是guest
注意,rabbitmq占用两个端口
15672:webUI
5672:程序连接的端口
-----------------------------------------------基本准备完成--------------------------------------------------------------
三.快速入门
rabbitmq相关依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.18.0</version>
</dependency>
1. 单向发送
basicPublish()
RabbitMQ 中,basicPublish
方法是用于发布消息到指定队列的。该方法接受四个参数:
- exchange:指定消息被发布到的交换机(exchange)的名称。如果没有指定交换机,消息将发布到默认交换机(default_exchange)。
routing_key
:指定消息将被路由到的队列(queue)的名称。如果消息未指定路由键,将发送到队列中名为amq.gen-{int}
的临时队列。body
:指定要发布的消息的字节数组。properties
:指定消息的属性,如消息类型、优先级、TTL 等。
其中,exchange
和 routing_key
参数用于指定消息的路由信息,body
参数用于指定要发布的消息内容,而 properties
参数用于指定消息的其他属性。
queueDeclare()
channel.queueDeclare() 是用于声明一个队列(queue)的状态。该方法的参数包括:
-
queueName:队列的名称。注意:同名称的队列只能用同样的参数创建一次
-
durable:一个布尔值,表示队列是否持久化。如果为true,那么这个队列会保留在服务器关闭或重启后,不会丢失队列中的消息。
-
exclusive:一个布尔值,表示这个队列是否是唯一的。如果为true,那么这个队列只属于当前连接的客户端。
-
autoDelete:一个布尔值,表示当队列不再被使用时,是否自动删除。如果为true,那么当队列不再被使用时,服务器会自动删除它。
-
arguments:一个对象,可以包含一些额外的参数。
这些参数可以帮助你控制队列的属性,如持久化、唯一性、自动删除等。这些属性可以根据你的需求进行调整,以适应不同的场景。
basicConsume
()
RabbitMQ 中,basicConsume
方法用于从指定的队列中消费消息。该方法的参数如下:
String queueName
:这是要消费消息的队列的名称。boolean autoAck
:如果为 true,消息将自动确认并被消费。如果为 false,则需要在调用basicConsume
方法时显式确认消息。Consumer callback
:这是一个回调函数,用于处理接收到的消息。Array args
:这是一个可选参数数组,可以用于传递额外的参数。
其中,queueName
参数指定要消费消息的队列的名称,autoAck
参数决定是否在接收消息时自动确认消息,callback
参数是一个回调函数,用于处理接收到的消息,而 args
参数是一个可选的参数数组,可以用于传递额外的参数。这些参数共同决定了如何从队列中消费消息。
-------------------------------------------------生产者代码--------------------------------------------------------------
public class SingleSend {
//声明队列的名称
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
//创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
//channel是操作消息队列的client
Channel channel = connection.createChannel()) {
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
//发布消息
channel.basicPublish("", QUEUE_NAME, null,
message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
-------------------------------------------------消费者代码--------------------------------------------------------------
public class SingleRecv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
//创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//创建队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//定义如何处理消息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println(" [x] Received '" + message + "'");
};
//消费消息,持续堵塞
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
生产者向“hello”的QUEUE队列发送消息,消费者从“hello”的QUEUE队列取消息
监控面板auto ack有峰值,消费者成功取出消息并确认(basicConsume
()的boolean autoAck参数为true,消息自动确认并消费
)了!
2. 多消费者
队列持久化
参数durable:true
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
消息持久化
MessageProperties.PERSISTENT_TEXT_PLAIN 是 RabbitMQ 中消息属性的一部分,用于指定消息的持久化类型。当消息被设置为 PERSISTENT_TEXT_PLAIN 类型时,这意味着消息将被存储在磁盘上,即使在服务器关闭或重启后也不会丢失。这对于需要长期存储的消息非常有用,例如日志或事件记录。
参数BasicProperties:MessageProperties.PERSISTENT_TEXT_PLAIN channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
basicQos()
channel.basicQos(int number)用于设置通道的 QoS(服务质量)。这个方法将指定的消息数量设置为通道的 QoS,意味着这个通道将生产并传输特定数量的消息
消息确认机制
为了保证消息被成功消费,rabbitmq提供了消息确认机制,当消费者成功接收到消息,要给反馈
- ack:消费成功
- nack:消费失败
- reject:拒绝
消费者告诉rabbitmq服务器消费成功,服务器才会正确移除消息
- 如果配置autoack为true,则消费者接收到消息则会自动确认
- 建议手动确认消息消费成功
-
channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, consumerTag -> { });
①指定确定某条消息,第二个参数multiple批量确认
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
②指定消息接收失败,第二个参数multiple批量确认,第三个参数requeue是否丢弃该消息
channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false,false);
③指定拒绝某条消息,第二个参数requeue是否丢弃该消息
channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
-------------------------------------------------生产者代码--------------------------------------------------------------
public class MultiProducer {
private static final String TASK_QUEUE_NAME = "multi_queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
//开启队列持久化
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
//手动控制发消息的内容
String message = scanner.nextLine();
//开启消息持久化
channel.basicPublish("", TASK_QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
------------------------------------------------消费者代码--------------------------------------------------------------
public class MultiConsumer {
private static final String TASK_QUEUE_NAME = "multi_queue";
public static void main(String[] argv) throws Exception {
//获取连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
final Connection connection = factory.newConnection();
//声明队列 两个消费者
for (int i = 0; i < 2; i++) {
final Channel channel = connection.createChannel();
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
System.out.println(" 编号:[*] Waiting for messages. To exit press CTRL+C");
//控制单个消费者的处理任务挤压数
channel.basicQos(1);
//定义如何处理消息
int finalI = i;
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
//处理工作
System.out.println(" [x] Received '" + "编号:" + finalI + ":" + message + "'");
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
System.out.println("[x] done");
};
//开启消费监听
channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
}
}
控制台测试结果
3.交换机
一个生产者给多个队列发消息
交换机(X)作用:提供消息转发功能
交换机-队列绑定规则
channel1.queueBind(queueName, EXCHANGE_NAME, "");
-
queueName:要绑定的队列名称。
-
exchangeName:要绑定的交换器名称。这个交换器必须已经存在并且具有绑定该队列的键。
-
routingKey:用于将消息路由到交换器的键。当有消息发送到绑定到该队列和交换器的路由键时,消息将被路由到队列中。
交换机类别
fanout,direct,topic,headers
Fanout交换机
特点:消息会被转发到所有绑定该交换机的队列
适用场景:发布订阅场景
-----------------------------------------------生产者代码-----------------------------------------------------------------
生产者与交换机绑定
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
- exchange:要使用的交换器的名称。如果为空字符串,消息将发送到绑定到队列的默认交换器。
- routingKey:消息的路由键,用于将消息路由到交换器。
- properties:消息的属性,可以包含有关消息的元数据。这些属性可以包括消息的优先级、TTL(生存时间)等
- body:消息的主体,包含要发送的实际数据。
public class FanoutProducer {
private static final String EXCHANGE_NAME = "fanout_queue";
public static void main(String[] argv) throws Exception {
//创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
//交换机的声明-名称及类别
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.nextLine();
//消息的发布及绑定交换机
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
}
-----------------------------------------------消费者代码-----------------------------------------------------------------
public class FanoutConsumer {
private static final String EXCHANGE_NAME = "fanout_queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel1 = connection.createChannel();
Channel channel2 = connection.createChannel();
//声明交换机(名称及类别)
channel1.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
//声明“xiaowang_queue”的消息队列
String queueName = "xiaowang_queue";
channel1.queueDeclare(queueName,true,false,false,null);
//“xiaowang_queue”的消息队列与交换机绑定
channel1.queueBind(queueName, EXCHANGE_NAME, "");
//声明“xiaoli_queue”的消息队列
String queueName2 = "xiaoli_queue";
channel2.queueDeclare(queueName2,true,false,false,null);
//“xiaoli_queue”的消息队列与交换机绑定
channel2.queueBind(queueName2, EXCHANGE_NAME, "");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//“小王”消费者的处理逻辑
DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [小王] Received '" + message + "'");
};
//“小李”消费者的处理逻辑
DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [小李] Received '" + message + "'");
};
channel1.basicConsume(queueName, true, deliverCallback1, consumerTag -> { });
channel2.basicConsume(queueName2, true, deliverCallback2, consumerTag -> { });
}
}
注意:生产者和消费者要绑定同一个交换机
先有队列,才可以绑定交换机
效果:生产者发送消息,消费者都可以接收到消息
Direct交换机
交换机把xx消息发送给指定队列
routingKey:路由键,控制交换机把某类消息发送给指定队列
路由键绑定关系:完全匹配字符串
示例场景:
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
routingKey交换机发送至指定队列的路由键
--------------------------------------------------生产者代码---------------------------------------------------------------
public class DirectProducer {
private static final String EXCHANGE_NAME = "direct_queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
//声明direct类别交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String userInput = scanner.nextLine();
String[] strings = userInput.split(" ");
if (strings.length < 1){
continue;
}
String message = strings[0];
String routingKey = strings[1];
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "',routingKey:" + routingKey);
}
}
}
}
--------------------------------------------------消费者代码---------------------------------------------------------------
public class DirectConsumer {
private static final String EXCHANGE_NAME = "direct_queue";
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, "direct");
String queueName1 = "xiaowang_direct_queue";
channel.queueDeclare(queueName1, true, false, false, null);
channel.queueBind(queueName1, EXCHANGE_NAME, "xiaowang");
String queueName2 = "xiaoli_direct_queue";
channel.queueDeclare(queueName2, true, false, false, null);
channel.queueBind(queueName2, EXCHANGE_NAME, "xiaoli");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [小王direct队列] Received '" +
delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
};
DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [小李direct队列] Received '" +
delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
};
channel.basicConsume(queueName1, true, deliverCallback1, consumerTag -> {
});
channel.basicConsume(queueName2, true, deliverCallback2, consumerTag -> {
});
}
}
控制台测试:
路由键与队列相匹配
若发送的消息无与之匹配的路由键,情况如何?
---无消费者应答
Topic交换机
特点:消息根据模糊的路由键转发到相应队列
绑定规则:单词间“.”隔开
- *:表示一个单词(*.apple则a.apple,b.apple则可以匹配)
- #:表示0个或者多个单词(#.apple则a.b.apple,apple则可以匹配)--
--案例 ---
-----------------------------------------------------生产者 ----------------------------------------------------------------
public class TopicProducer {
private static final String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String userInput = scanner.nextLine();
String[] strings = userInput.split(" ");
if (strings.length < 1){
continue;
}
String message = strings[0];
String routingKey = strings[1];
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "',routingKey:" + routingKey);
}
}
}
}
-----------------------------------------------------消费者 ----------------------------------------------------------------
public class TopicConsumer {
private static final String EXCHANGE_NAME = "topic_exchange";
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, "topic");
String queueName1 = "front_topic_queue";
channel.queueDeclare(queueName1, true, false, false, null);
channel.queueBind(queueName1, EXCHANGE_NAME, "#.前端.#");
String queueName2 = "backend_topic_queue";
channel.queueDeclare(queueName2, true, false, false, null);
channel.queueBind(queueName2, EXCHANGE_NAME, "#.后端.#");
String queueName3 = "product_topic_queue";
channel.queueDeclare(queueName3, true, false, false, null);
channel.queueBind(queueName3, EXCHANGE_NAME, "#.产品.#");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [前端] Received '" +
"'message:'" + message + "'," + "routingKey:" + delivery.getEnvelope().getRoutingKey() );
};
DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [后端] Received '" +
"'message:'" + message + "'," + "routingKey:" + delivery.getEnvelope().getRoutingKey() );
};
DeliverCallback deliverCallback3 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [产品] Received '" +
"'message:'" + message + "'," + "routingKey:" + delivery.getEnvelope().getRoutingKey() );
};
channel.basicConsume(queueName1, true, deliverCallback1, consumerTag -> {
});
channel.basicConsume(queueName2, true, deliverCallback2, consumerTag -> {
}); channel.basicConsume(queueName3, true, deliverCallback3, consumerTag -> {
});
}
}
---控制台测试
Header交换机
链接:RabbitMQ tutorial - Remote procedure call (RPC) — RabbitMQ
四.核心特性
4.1消息过期机制
4.1.1队列中消息过期机制
官方文档:Time-To-Live and Expiration — RabbitMQ
给队列的每一个消息指定过期时间,消息在该时间内未被消费则过期
注意:RabbitMQ中设置队列过期时间的参数键(key)是固定的。对于声明式队列设置,可以使用x-message-ttl
来设定消息的过期时间。
//声明队列消息的过期时间
Map<String,Object> args = new HashMap<>();
args.put("x-message-ttl",5000);
channel.queueDeclare(QUEUE_NAME, false, false, false, args);
4.1.2单条消息过期机制
//单条消息的过期时间设定
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.expiration("5000")
.build();
channel.basicPublish("", QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
4.2消息确认机制
见上文 三 .快速入门 ---> 2.多消费者 --->消息确认机制
4.3死信队列
在RabbitMQ中,死信队列是一种特殊的队列,用于处理不能被正常处理的消息。当消息在队列中满足以下任一情况时,它会被转移到死信队列:
- 消息被拒绝(basic.reject)或被 nack(basic.nack),并且 requeue = false
- 消息的 TTL(Time to Live)过期
- 队列达到最大长度
死信队列的用途主要是作为一个备用机制,以确保当消息在主队列中无法被正确处理时,它们能够被安全地存储并可供后续处理。
如果配置了死信队列,当消息在主队列中变成死信后,它会被重新publish到与主队列关联的死信交换机(Dead Letter Exchange,简称DLX)。死信交换机将死信投递到一个队列上,这个队列就是死信队列。
通过使用死信队列和死信交换机,可以实现消息的可靠传递和处理,提高系统的健壮性和容错能力。
给失败后容错的消息 绑定 死信交换机
Map<String, Object> args1 = new HashMap<String, Object>()
//指定死信队列绑定到哪一个死信交换机
args1.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
//指定死信队列绑定到哪一个死信队列
args1.put("x-dead-letter-routing-key","boss");
String queueName1 = "xiaowang_work_direct_queue";
channel.queueDeclare(queueName1, true, false, false, args1);
channel.queueBind(queueName1, WORK_EXCHANGE_NAME, "xiaowang");
---案例---
---生产者代码
public class DeadLetterDirectProducer {
private static final String WORK_EXCHANGE_NAME = "work_direct_queue";
private static final String DEAD_EXCHANGE_NAME = "dead_direct_queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
channel.exchangeDeclare(WORK_EXCHANGE_NAME, "direct");
channel.exchangeDeclare(DEAD_EXCHANGE_NAME, "direct");
//创建死信队列
String queueName1 = "boss_dead_direct_queue";
channel.queueDeclare(queueName1, true, false, false, null);
channel.queueBind(queueName1, DEAD_EXCHANGE_NAME, "boss");
String queueName2 = "waibao_dead_direct_queue";
channel.queueDeclare(queueName2, true, false, false, null);
channel.queueBind(queueName2, DEAD_EXCHANGE_NAME, "waibao");
DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [boss老板] Received '" + ",RoutingKey:" +
delivery.getEnvelope().getRoutingKey() + "',message'" + message + "'");
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [waibao外包] Received '" + ",RoutingKey:" +
delivery.getEnvelope().getRoutingKey() + "',message'" + message + "'");
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
channel.basicConsume(queueName1, false, deliverCallback1, consumerTag -> {
});
channel.basicConsume(queueName2, false, deliverCallback2, consumerTag -> {
});
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String userInput = scanner.nextLine();
String[] strings = userInput.split(" ");
if (strings.length < 1){
continue;
}
String message = strings[0];
String routingKey = strings[1];
channel.basicPublish(WORK_EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "',routingKey:" + routingKey);
}
}
}
}
---消费者代码
public class DeadLetterDirectConsumer {
private static final String WORK_EXCHANGE_NAME = "work_direct_queue";
private static final String DEAD_EXCHANGE_NAME = "dead_direct_queue";
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(WORK_EXCHANGE_NAME, "direct");
//声明死信交换机
channel.exchangeDeclare(DEAD_EXCHANGE_NAME, "direct");
Map<String, Object> args1 = new HashMap<String, Object>();
//指定死信队列绑定到哪一个死信交换机
args1.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
//指定死信队列绑定到哪一个死信队列
args1.put("x-dead-letter-routing-key","boss");
String queueName1 = "xiaowang_work_direct_queue";
channel.queueDeclare(queueName1, true, false, false, args1);
channel.queueBind(queueName1, WORK_EXCHANGE_NAME, "xiaowang");
Map<String, Object> args2 = new HashMap<String, Object>();
args2.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
args2.put("x-dead-letter-routing-key","waibao");
String queueName2 = "xiaoli_direct_work_queue";
channel.queueDeclare(queueName2, true, false, false, args2);
channel.queueBind(queueName2, WORK_EXCHANGE_NAME, "xiaoli");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback1 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
//拒绝消息
channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,false);
System.out.println(" [小王direct队列] Received '" +
delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
};
DeliverCallback deliverCallback2 = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
//拒绝消息
channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,false);
System.out.println(" [小李direct队列] Received '" +
delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
};
channel.basicConsume(queueName1, false, deliverCallback1, consumerTag -> {
});
channel.basicConsume(queueName2, false, deliverCallback2, consumerTag -> {
});
}
}
交换机绑定关系
死信交换机绑定关系
---测试
五.项目中使用rabbitmq
- 使用官方客户端
- 使用springboot整合的框架 Spring Boot RabbitMQ Start
Spring Boot RabbitMQ Start
5.1引入依赖
注意:Spring Boot RabbitMQ Start 版本 要和spring boot 版本一致
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.7.2</version>
</dependency>
5.2配置RabbitMQ
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
5.3 创建交换机和队列
项目启动前创建一次即可!
public class InitQueueMain {
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
String EXCHANGE_NAME = "code_exchange";
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String QUEUE_NAME = "code_queue";
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"myRoutingKey");
}catch (Exception e){
e.printStackTrace();
}
}
}
5.4生产者代码
@Configuration
@Slf4j
public class MyMessageProducer {
@Resource
private RabbitTemplate rabbitTemplate;
void sendMessage(String exchange_name,String routing_key,String message){
rabbitTemplate.convertAndSend(exchange_name,routing_key,message);
}
}
5.5消费者代码
@Component
@Slf4j
public class MyMessageConsumer {
@SneakyThrows
@RabbitListener(queues = {"code_queue"},ackMode = "MANUAL")
public void receiveMessage(String message, Channel channel ,@Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag){
log.info("receiveMessage,message:" + message);
channel.basicAck(deliveryTag,false);
}
}
5.6单元测试执行
@SpringBootTest
class MyMessageProducerTest {
@Resource
private MyMessageProducer myMessageProducer;
@Test
void sendMessage() {
myMessageProducer.sendMessage("code_exchange","myRoutingKey","你好,springboot客户端整合rabbitmq的test");
}
}
结果如下