Rabbit MQ
初识
什么是MQ
MQ(message queue),从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已,还是一种跨进程的通信机制,用于上下游传递消息。在互联网架构中,MQ是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用了MQ之后,消息发送上游只需要依赖MQ,不用依赖其他服务。
MQ的作用
流量消峰
举个例子,如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正 常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限 制订单超过一万后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分 散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体 验要好。
应用解耦
以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合 调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于 消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在 这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流 系统恢复后,继续处理订单信息即可,中单用户感受不到物流系统的故障,提升系统的可用性
异步处理
有些服务间调用是异步的,例如 A 调用 B,B 需要花费很长时间执行,但是 A 需要知道 B 什么时候可 以执行完,以前一般有两种方式,A 过一段时间去调用 B 的查询 api 查询。或者 A 提供一个 callback api, B 执行完之后调用 api 通知 A 服务。这两种方式都不是很优雅,使用消息总线,可以很方便解决这个问题, A 调用 B 服务后,只需要监听 B 处理完成的消息,当 B 处理完成后,会发送一条消息给 MQ,MQ 会将此 消息转发给 A 服务。这样 A 服务既不用循环调用 B 的查询 api,也不用提供 callback api。同样 B 服务也不 用做这些操作。A 服务还能及时的得到异步处理成功的消息。
入门
Rabbit MQ的基本概念
Rabbit MQ 是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点,当你要发送一个包 裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里,按照这种逻辑 RabbitMQ 是 一个快递站,一个快递员帮你传递快件。Rabbit MQ 与快递站的主要区别在于,它不处理快件而是接收, 存储和转发消息数据。
用户发起订单->MQ->实际处理接口
四大核心概念
- 生产者
产生数据发送消息的程序是生产者
- 交换机
交换机是 RabbitMQ 非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息 推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推 送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定
- 队列
队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存 储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可 以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。这就是我们使用队列的方式
- 消费者
消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。请注意生产者,消费 者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。
名词解释
- 中间人 broker
接收和分发消息的应用,RabbitMQ Server 就是 Message Broker
- 虚拟机 Virtual host
出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似 于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等
本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、绑定、交换器和权限控制;
- 连接 Connection
publisher/consumer 和 broker 之间的 TCP 连接
- 信道 Channel
如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程 序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客 户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
- 交换机 Exchange
message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发 消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
- 队列Queue
消息最终被送到这里等待 consumer 取走
- Binding
exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保 存到 exchange 中的查询表中,用于 message 的分发依据
安装
docker安装
版本号带
management的带有网页功能,不携带的则没有。
下载镜像
docker pull rabbitmq:management创建数据卷
docker volume create rabbitmq-home创建并运行容器
15672端口:RabbitMQ的管理页面端口
5672端口:RabbitMQ的消息接收端口
RABBITMQ_DEFAULT_USER环境变量:指定RabbitMQ的用户名
RABBITMQ_DEFAULT_PASS环境变量:指定RabbitMQ的密码docker run -id --name=rabbitmq -v rabbitmq-home:/var/lib/rabbitmq -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=root -e RABBITMQ_DEFAULT_PASS=123456 rabbitmq:management访问容器
docker exec -it rabbitmq bash 可以进入shell进行具体操作
使用服务器ip:15672即可访问网页版RabbitMQ
RrabbitMq命令
# 创建用户
rabbitmqctl add_user <username> <password>
# 设置用户角色
rabbitmqctl set_user_tags admin administrator
# 设置用户权限
set_permissions [-p <vhostpath>] <username> <conf> <write> <read>
# 设置用户的所有权限
rabbitmqctl set_permissions -p / <username> ".*" ".*" ".*"
# 查看所有的用户
rabbitmqctl list_users
Hello World
-
生产者
ConnectionFactory factory = new ConnectionFactory(); factory.setHost("192.168.200.101"); factory.setUsername("root"); factory.setPassword("123456"); //channel 实现了自动 close 接口 自动关闭 不需要显示关闭 try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { /** * 生成一个队列 * 1.队列名称 * 2.队列里面的消息是否持久化 默认消息存储在内存中 * 3.该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费 * 4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除 * 5.其他参数 */ channel.queueDeclare(QUEUE_NAME, false, false, false, null); String message = "hello world"; /** * 发送一个消息 * 1.发送到那个交换机 * 2.路由的 key 是哪个 * 3.其他的参数信息 * 4.发送消息的消息体 */ channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); System.out.println("消息发送完毕"); -
消费者
Channel channel = RabbitMQUtils.getChannel(); System.out.println("等待接收消息...."); //推送的消息如何进行消费的接口回调 DeliverCallback deliverCallback=(consumerTag,delivery)->{ System.out.println("开始消费"); String message= new String(delivery.getBody()); System.out.println(message); }; //取消消费的一个回调接口 如在消费的时候队列被删除掉了 CancelCallback cancelCallback=(consumerTag)->{ System.out.println("消息消费被中断"); }; /** * 消费者消费消息 * 1.消费哪个队列 * 2.消费成功之后是否要自动应答 true 代表自动应答 false 手动应答 * 3.消费者未成功消费的回调 */ channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
工作队列模型
工作队列(Work Queues):也称为任务队列(Task Queues)。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费就会消失,因此任务是不会被重复执行的,
轮询平均分配
默认情况下, 如果使用了多个消费者同时消费一个消息队列,RabbitMQ会将消息队列中的消息轮询平均分配给这两个消费者。
生产者、消费者代码同直连模式一样。
能者多劳分配
轮询平均分配的模式下,如果消费者1的执行速度慢,而消费者2执行速度快,则容易在消费者1服务器出现阻塞。
可以将消费者的接收到消息就自动消息确认,改为消费逻辑执行完之后手动确认。(消息自动确认对应
basicConsume方法的第二个参数。 如果消费者自动消息确认为true,则从MQ过来的消息在消费者执行前就会被自动确认掉。如果自动确认改为false,则消费者不再进行消息确认的操作,需要通过编码的方式在消费逻辑执行完手动将消息进行确认)Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); // 默认为0,则为轮训分配,修改为1则谁有空谁消费 channel.basicQos(1); channel.queueDeclare("work", true, false, false, null); channel.basicConsume("work", false, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("接收到消息:" + new String(body)); channel.basicAck(envelope.getDeliveryTag(), false); } });
权重
将basicQos设置为大于1以上则为按权重分配。代码同上
channel.basicQos(5);
消息应答
消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成 了部分突然它挂掉了,会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息,便立即将该消 息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续 发送给该消费这的消息,因为它无法接收到。
为了保证消息在发送过程中不丢失,rabbitmq 引入消息应答机制,消息应答就是:消费者在接 收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。
自动应答
消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者 channel 关闭,那么消息就丢失了,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制, 当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终 使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并 以某种速率能够处理这些消息的情况下使用。
手动应答
// 第二个参数选择false就是关闭自动应答,选择手动应答
channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
//用于肯定确认,RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
channel.basicAck();
// 用于否定应答一个或多个消息,并可选择将消息重新排队以便稍后重新投递给消费者。第三个参数为true表示消息将重新排队。
channel.basicNack(long deliveryTag, boolean multiple, boolean requeue)
//用于否定应答单个消息,并可选择将消息重新排队以便稍后重新投递给消费者。
channel.basicReject(long deliveryTag, boolean requeue)
队列持久化
默认情况下,exchange、queue、message 等数据都是存储在内存中的,这意味着如果 RabbitMQ 重启、关闭、宕机时所有的信息都将丢失。
RabbitMQ 提供了持久化来解决这个问题,持久化后,如果 RabbitMQ 发送 重启、关闭、宕机,下次起到时 RabbitMQ 会从硬盘中恢复exchange、queue、message 等数据。
队列持久化时,若队列已经存在,则需要把之前未声明持久化的队列先删除才能持久化。
交换机的持久化
// 三个参数分别为 交换器名、交换器类型、是否持久化 channel.exchangeDeclare(EXCHANGE_NAME, "topic", true);队列的持久化
// 参数1 queue :队列名 // 参数2 durable :是否持久化 // 参数3 exclusive :仅创建者可以使用的私有队列,断开后自动删除 // 参数4 autoDelete : 当所有消费客户端连接断开后,是否自动删除队列 // 参数5 arguments channel.queueDeclare(QUEUE_NAME, true, false, false, null);消息的持久化
// 参数1 exchange :交换器 // 参数2 routingKey : 路由键 // 参数3 props : 消息的其他参数,其中 MessageProperties.PERSISTENT_TEXT_PLAIN 表示持久化 // 参数4 body : 消息体 channel.basicPublish("", queue_name, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());发布确认(没有这一条以上无法做到真正的持久化)
// 开启发布确认方法 Channel channel = RabbitMQUtils.getChannel(); channel.confirmSelect(); // 1.单个确认,waitForConfirms返回的就是结果 boolean flag = channel.waitForConfirms(); // 2.批量确认,使用与单个一致,但是使用计数器,在提交N次后获取是否成功。速度比单个快,但无法定位到哪一条没有确认成功。 for (int i = 1; i <= 1000; i++) { String message = "Hello World" + i; channel.basicPublish("",QUEUE_NAME,null,message.getBytes()); if (i % 100 == 0){ boolean flag = channel.waitForConfirms(); } }// 3.异步批量确认,ConcurrentSkipListMap存储所有发布的数据,成功后删除,失败则从map里获取对应的message进行后续操作。 ConcurrentSkipListMap<Long,String> concurrentSkipListMap = new ConcurrentSkipListMap(); try (Channel channel = RabbitMQUtils.getChannel()){ channel.confirmSelect(); ConfirmCallback ackCallback = (deliveryTag,multiple)->{ // 发布成功回调函数 ConcurrentNavigableMap<Long, String> longStringConcurrentNavigableMap = concurrentSkipListMap.headMap(deliveryTag,true); longStringConcurrentNavigableMap.clear();// 删除已确认的消息 }; ConfirmCallback nackCallback = (deliveryTag,multiple)->{ // 发布失败回调函数 String message = concurrentSkipListMap.get(deliveryTag); System.out.println("消息发送失败,消息内容为:" + message); }; channel.addConfirmListener(ackCallback,nackCallback); for (int i = 0; i < 1000; i++) { String message = "Hello World" + i; channel.basicPublish("",QUEUE_NAME,null,message.getBytes()); concurrentSkipListMap.put(channel.getNextPublishSeqNo(),message); } } catch (IOException e) { throw new RuntimeException(e); } catch (TimeoutException e) { throw new RuntimeException(e); }
交换机
RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。
相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来 自生产者的消息,另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消 息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。
交换机的类型
-
direct(默认)
direct为默认的交换器类型,如果路由key匹配的话,消息就投递到相应的队列。
-
fanout 发布/订阅模式
fanout是一种发布/订阅模式的交换器,当你发送一条消息的时候,交换器会把消息广播到所有附加到这个交换器的队列上
-
topic 匹配订阅模式
-
headers
headers交换器允许你匹配AMQP消息的header而非路由键,除此之外headers交换器和direct交换器完全一致,但性能却很差,几乎用不到
消息模型
广播
- 可以有多个消费者
- 每个消费者有自己的queue(队列)
- 每个队列要绑定到Exchange(交换机)
- 生产者发送的消息,只能发送到交换机。交换机来决定要发给哪个队列,生产者无法决定。
- 交换机把消息发送给绑定过的所有队列
- 队列的消费者都能拿到消息,实现一条消息被多个消费者消费
// 生产者
Channel channel = RabbitMQUtils.getChannel();
// 声明一个交换器,类型为 FANOUT(广播模式)
channel.exchangeDeclare("logsExchange", BuiltinExchangeType.FANOUT);
System.out.println("开始发布消息");
for (int i = 0; i < 10; i++) {
String message = "hello world" + i;
// 发布消息到交换器,第2个参数在发布订阅模式下无意义,可以设置为空字符串
channel.basicPublish("logsExchange", "", null, message.getBytes());
}
// 消费者
Channel channel = RabbitMQUtils.getChannel();
// 声明一个匿名队列,并获取队列名
String queueName = channel.queueDeclare().getQueue();
// 声明一个交换器,类型为 FANOUT(广播模式)
channel.exchangeDeclare("logsExchange", BuiltinExchangeType.FANOUT);
// 将队列绑定到交换器上,不指定路由键
channel.queueBind(queueName, "logsExchange", "");
// 消息的处理逻辑
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
System.out.println("FanoutConsumer01: " + new String(delivery.getBody()));
};
// 取消消费的回调逻辑
CancelCallback cancelCallback = (consumerTag) -> {
};
System.out.println("开始接受消息:");
// 开始消费消息,设置消息的处理回调和取消回调
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
发布订阅
- 队列与交换机的绑定,不能是任意绑定,而是要指定一个 RoutingKey(路由key)
- 消息的发送方在向交换机发送消息时,也必须指定消息的 RoutingKey
- 交换机不再把消息交给每一个绑定的队列,而是根据消息的 RoutingKey 进行判断,只有队列的 RoutingKey 与消息的RoutingKey完全一致,才会接收到消息。
- 一个消费者可以订阅多个消息(RoutingKey)
// 生产者
Channel channel = RabbitMQUtils.getChannel();
channel.exchangeDeclare("direct", BuiltinExchangeType.DIRECT);
System.out.println("开始发布消息");
for (int i = 0; i < 10; i++) {
String message = "hello world" + i;
if (i %2 ==0){
channel.basicPublish("direct","success",null,message.getBytes());
}else{
channel.basicPublish("direct","error",null,message.getBytes());
}
}
//消费者
Channel channel = RabbitMQUtils.getChannel();
String queueName = channel.queueDeclare().getQueue();
// 绑定多个routingkey
channel.queueBind(queueName,"direct","success");
channel.queueBind(queueName,"direct","error");
channel.exchangeDeclare("direct", BuiltinExchangeType.DIRECT);
DeliverCallback deliverCallback = (consumerTag,message) -> {
System.out.println("01" + new String(message.getBody()));
};
CancelCallback cancelCallback = (consumerTag)->{
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
路由
topic交换器运行和fanout类似,但是可以更灵活的匹配自己想要订阅的信息,这个时候routingKey路由键就排上用场了,使用路由键进行消息(规则)匹配,最多不能超过 255 个字节。
*(星号)可以代替一个单词
#(井号)可以替代零个或多个单词
Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!这种模型Routingkey 一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如:item.insert
死信队列
RabbitMQ 里,当消息在队列中变成死信(消费者无法正常处理的消息)之后,它会被重新投递到一个交换机上(即死信交换机),死信交换机上绑定的消费队列就是死信队列。
死信产生需要满足如下条件:
- 消息被消费者手动拒绝接收,并且
requeue(重新加入队列)策略为 False;- 消息已经过期(TTL);
- 队列达到最大长度,消息装不下了。
当死信产生时,如果我们定义了一个
死信交换机(其实就是一个普通的交换机,只是用于处理死信,所以叫死信交换机),然后在死信交换机上绑定了一个队列(称作死信队列)。最后,如果死信队列有消费者监听时,死信消息的处理就会和正常业务消息一样,从交换机到队列,再由
死信消费者(监听死信队列的消费者)正常消费。
// 消费者 超时模拟
public class Producer {
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static final String DEAD_EXCHANGE = "dead_exchange";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMQUtils.getChannel();
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
// 设置消息超时时间
AMQP.BasicProperties properties = new
AMQP.BasicProperties().builder().expiration("10000").build();
System.out.println("开始发布消息");
for (int i = 0; i < 10; i++) {
String message = "hello world" + i;
System.out.println(message);
// 添加配置properties channel.basicPublish(NORMAL_EXCHANGE,"normalRoutingKey",properties,message.getBytes());
}
}
}
// 普通消费者
public class NormalConsumer {
private static final String NORMAL_EXCHANGE = "normal_exchange";
// //死信交换机名称
private static final String DEAD_EXCHANGE = "dead_exchange";
public static void main(String[] argv) throws Exception {
String NORMAL_EXCHANGE = "normal_exchange";
//死信交换机名称
String DEAD_EXCHANGE = "dead_exchange";
Channel channel = RabbitMQUtils.getChannel();
//声明死信和普通交换机 类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明死信队列
String deadQueue = "dead-queue";
String deadRoutingKey = "deadRoutingKey";
String normalRoutingKey = "normalRoutingKey";
channel.queueDeclare(deadQueue, false, false, false, null);
//死信队列绑定死信交换机与 routingkey
channel.queueBind(deadQueue, DEAD_EXCHANGE, deadRoutingKey);
//正常队列绑定死信队列信息
Map<String, Object> params = new HashMap<>();
//正常队列设置死信交换机 参数 key 是固定值
params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
//正常队列设置死信 routing-key 参数 key 是固定值
params.put("x-dead-letter-routing-key", deadRoutingKey);
String normalQueue = "normal-queue";
channel.queueDeclare(normalQueue, false, false, false, params);
channel.queueBind(normalQueue, NORMAL_EXCHANGE, normalRoutingKey);
System.out.println("等待接收消息.....");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("normalConsumer 接收到消息" + message);
};
channel.basicConsume(normalQueue, true, deliverCallback, consumerTag -> {
});
}
}
// 死信消费者
public class DeadConsumer {
private static final String DEAD_EXCHANGE = "dead_exchange";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtils.getChannel();
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
String deadQueue = "dead-queue";
channel.queueDeclare(deadQueue, false, false, false, null);
channel.queueBind(deadQueue, DEAD_EXCHANGE, "deadRoutingKey");
System.out.println("等待接收死信队列消息.....");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("Consumer02 接收死信队列的消息" + message);
};
channel.basicConsume(deadQueue, true, deliverCallback, consumerTag -> {
});
}
}
整合SpringBoot
配置文件
spring:
rabbitmq:
host: 192.168.200.101
port: 5672
username: root
password: 123456
# virtual-host: /ems
jar包
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.10.0</version>
</dependency>
<!--操作文件流的一个依赖-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!--RabbitMQ 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--RabbitMQ 测试依赖-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
config
package com.yibai.mq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class TtlQueueConfig {
public static final String X_EXCHANGE = "X";
public static final String QUEUE_A = "QA";
public static final String QUEUE_B = "QB";
public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
public static final String DEAD_LETTER_QUEUE = "QD";
// 声明 xExchange
@Bean("xExchange")
public DirectExchange xExchange(){
return new DirectExchange(X_EXCHANGE);
}
// 声明 xExchange
@Bean("yExchange")
public DirectExchange yExchange(){
return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
}
//声明队列 A ttl 为 10s 并绑定到对应的死信交换机
@Bean("queueA")
public Queue queueA(){
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", "YD");
//声明队列的 TTL
args.put("x-message-ttl", 10000);
return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
}
// 声明队列 A 绑定 X 交换机
@Bean
public Binding queueaBindingX(@Qualifier("queueA") Queue queueA,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueA).to(xExchange).with("XA");
}
//声明队列 B ttl 为 40s 并绑定到对应的死信交换机
@Bean("queueB")
public Queue queueB(){
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", "YD");
//声明队列的 TTL
args.put("x-message-ttl", 40000);
return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
}
//声明队列 B 绑定 X 交换机
@Bean
public Binding queuebBindingX(@Qualifier("queueB") Queue queue1B,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queue1B).to(xExchange).with("XB");
}
//声明死信队列 QD
@Bean("queueD")
public Queue queueD(){
return new Queue(DEAD_LETTER_QUEUE);
}
//声明死信队列 QD 绑定关系
@Bean
public Binding deadLetterBindingQAD(@Qualifier("queueD") Queue queueD,
@Qualifier("yExchange") DirectExchange yExchange){
return BindingBuilder.bind(queueD).to(yExchange).with("YD");
}
}
生产者
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping("/ttl/{messageBody}")
public Result sendMessageByTopic(@PathVariable("messageBody") String messageBody) {
log.info("消息为:"+messageBody);
rabbitTemplate.convertAndSend("X","XA",messageBody+":10S");
rabbitTemplate.convertAndSend("X","XB",messageBody+":40S");
return Result.ok();
}
消费者
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class DeadLetterQueueConsumer {
@RabbitListener(queues = "QD")
public void receiveD(Message message, Channel channel) {
String str = new String(message.getBody());
log.info("DeadLetterQueueConsumer receive message:{}", str);
}
}