RabbitMQ处理未被路由的消息
我们经常使用消息队列进行系统之间的解耦,日志记录等等。但是有时候我们在使用 RabbitMQ时,由于exchange、bindKey、routingKey没有设置正确,导致我们发送给交换器(exchange)的消息,由于没有正确的RoutingKey可能会存在一个消息丢失的情况,如果我们希望知道那些消息经过exchange之后,没有被正确的存入消息队列,那么应该如何进行处理。
方案一:使用 mandatory 参数配合 ReturnListener 来进行解决
方案二:使用备份交换器 (alternate exchange) 来进行解决
方案一介绍:
mandatory参数的含义:
true:表示当交换器无法根据自身的类型和路由键找到一个符合条件的队列时,那么RabbitMQ会调用 Basic.Return 命令将消息返回给生产者。生产者使用ReturnListener 来监听没有被正确路由到消息队列中的消息。
false:表示当交换器无法根据自身的类型和路由键找到一个服务条件的队列时,那么RabbitMQ会丢弃这个消息。
注意事项:
1、有时候发现即使 mandatory参数设置成 true,也没有进入 ReturnListener,那么这个可能是什么原因呢?其实这个可能是受RabbitMQ配置的内存和磁盘告警限制。(http://www.rabbitmq.com/alarms.html)
2、这是一个RabbitMQ配置的磁盘告警导致没有进入ReturnListener的例子。(http://rabbitmq.1065348.n5.nabble.com/ReturnListener-is-not-invoked-td24549.html)
示例代码:
/**
* RabbitMQ 生产者
* <pre>
* 1、ReturnListener 的使用。
* >> mandatory: 参数需要设置成 true , ReturnListener 才会生效。
* >> 用于获取到没有路由到消息队列中的消息。
* 2、ReturnListener 的注意事项 http://www.rabbitmq.com/alarms.html
* >> 受到内存和磁盘的限制
* >> http://rabbitmq.1065348.n5.nabble.com/ReturnListener-is-not-invoked-td24549.html(一个RabbitMQ disk_free_limit 参数导致ReturnListener没有进入的例子)
*
* </pre>
*
* @author huan.fu
* @date 2018/8/21 - 15:23
*/
public class RabbitProducer {
private static final String EXCHANGE_NAME = "exchange_demo";
private static final String ROUTING_KEY = "missing_routing_key";
private static final String BINDING_KEY = "bingkey_demo";
private static final String QUEUE_NAME = "queue_demo";
private static final String IP_ADDRESS = "140.143.237.224";
private static final int PORT = 5672;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(IP_ADDRESS);
connectionFactory.setPort(PORT);
connectionFactory.setUsername("root");
connectionFactory.setPassword("root");
try (
// 创建一个连接
Connection connection = connectionFactory.newConnection();
// 创建信道
Channel channel = connection.createChannel()
) {
// 创建一个 type="direct"持久化、非自动删除的交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, null);
// 创建一个 持久化、非排他的、非自动删除的交换器
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 将交换器与队列通过路由键绑定 使用 bindingKey
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY);
// 发送一条持久化消息
String message = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 没有被正确路由到消息队列的消息.mandatory参数设置成true";
try {
// 使用 routingKey
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, true, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
System.err.println("消息发送完成......");
} catch (IOException e) {
e.printStackTrace();
}
/**
* 处理生产者没有正确路由到消息队列的消息
* 这个可能不会生效:受到 rabbitmq 配置的内存和磁盘的限制 {@link http://www.rabbitmq.com/alarms.html}
*/
channel.addReturnListener((replyCode, replyText, exchange, routingKey, properties, body) -> {
System.out.println("replyCode:" + replyCode);
System.out.println("replyText:" + replyText);
System.out.println("exchange:" + exchange);
System.out.println("routingKey:" + routingKey);
System.out.println("properties:" + properties);
System.out.println("body:" + new String(body, StandardCharsets.UTF_8));
});
}
}
}
方案二介绍:
使用方案一,我们需要自己写ReturnListener,这样业务代码就变的复杂了,那么有没有一种简单的方法呢?那就是使用 备份交换器(Alternate Exchange)
声明交换器可以在channel.exchangeDeclare的时候 添加 alternate-exchange 参数来实现,交换器的类型建议声明成 fanout 类型,因为消息被重新发送到备份交换器时的路由键和从生产者出发的路由键是一致的。
示例代码:
public class RabbitProducer {
private static final String EXCHANGE_NAME = "exchange_demo";
private static final String BINDING_KEY = "bingkey_demo";
private static final String QUEUE_NAME = "queue_demo";
private static final String IP_ADDRESS = "140.143.237.224";
private static final int PORT = 5672;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(IP_ADDRESS);
connectionFactory.setPort(PORT);
connectionFactory.setUsername("root");
connectionFactory.setPassword("root");
try (
// 创建一个连接
Connection connection = connectionFactory.newConnection();
// 创建信道
Channel channel = connection.createChannel()
) {
Map<String, Object> arguments = new HashMap<>(16);
arguments.put("alternate-exchange", "backup-exchange");
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, arguments);
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY);
// 声明一个 fanout 类型的交换器,建议此处使用 fanout 类型的交换器
channel.exchangeDeclare("backup-exchange", "fanout", true, false, null);
// 消息没有被路由的之后存入的队列
channel.queueDeclare("unRoutingQueue", true, false, false, null);
channel.queueBind("unRoutingQueue", "backup-exchange", "");
// 发送一条持久化消息
String message = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 没有被正确的路由到消息队列,此时此消息会进入 unRoutingQueue";
try {
// 使用 routingKey
channel.basicPublish(EXCHANGE_NAME, "not-exists-routing-key", true, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
System.err.println("消息发送完成......");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
上例图解:
RabbitMQ处理未被路由的消息的更多相关文章
- RabbitMQ(4) 未路由的消息、TTL和死信
未路由的消息 当生产这发送的消息到达指定的交换器后,如果交换器无法根据自身类型.绑定的队列以及消息的路由键找到匹配的队列,默认情况下消息将被丢弃.可以通过两种方式 处理这种情况,一是在发送是设置man ...
- RabbitMQ不讲武德,发个消息也这么多花招
前言 本篇博客已被收录GitHub:https://zhouwenxing.github.io/ 文中所涉及的源码也已被收录GitHub:https://github.com/zhouwenxing/ ...
- RabbitMQ系列(四)RabbitMQ事务和Confirm发送方消息确认——深入解读
RabbitMQ事务和Confirm发送方消息确认--深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...
- RabbitMQ详解(二)------消息通信的概念
PS:近期在南宁出差,工作比较忙,所以更新会比较慢. 说到消息通信,可能我们首先会想到的是邮箱,QQ,微信,短信等等这些通信方式,这些通信方式都有发送者,接收者,还有一个中间存储离线消息的容器.但是这 ...
- RabbitMQ-消费者"未处理完的消息"丢失
一个关于客户端(消费者)开启自动应答,重启后"未处理消息丢失"的小坑.(主要是对RabbitMQ理解不够) 首先,申明一下: 本文所谓的 "丢失消息" 不是指服 ...
- RabbitMQ六种队列模式-路由模式
前言 RabbitMQ六种队列模式-简单队列RabbitMQ六种队列模式-工作队列RabbitMQ六种队列模式-发布订阅RabbitMQ六种队列模式-路由模式 [本文]RabbitMQ六种队列模式-主 ...
- RabbitMQ入门教程(十二):消息确认Ack
原文:RabbitMQ入门教程(十二):消息确认Ack 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csd ...
- RabbitMQ事务和Confirm发送方消息确认
RabbitMQ事务和Confirm发送方消息确认——深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...
- 一个基于RabbitMQ的可复用的事务消息方案
前提 分布式事务是微服务实践中一个比较棘手的问题,在笔者所实施的微服务实践方案中,都采用了折中或者规避强一致性的方案.参考Ebay多年前提出的本地消息表方案,基于RabbitMQ和MySQL(JDBC ...
随机推荐
- reeswitch http https ws wss nginx domain default port config
现代H5浏览器产业链越来越丰富,http+websocket+webrtc+sip组合已经是一种非常成熟的web原生音视频通讯解决方案 FreeSWITCH是一个开源的电话软交换平台,早在SIP年代就 ...
- Linux下运行bash脚本显示“: /usr/bin/env: "bash\r": 没有那个文件或目录
用 ./ 运行bash脚本文件出现 报错信息 /usr/bin/env: "bash\r": 没有那个文件或目录 错误原因:这主要是因为bash后面多了\r这个字符的原因.在lin ...
- pythonGUI-PySide2的使用笔记
用python开发跨平台的图形化界面,主流的有3种选择: Tkinter 基于Tk的Python库,Python官方标准库,稳定.发布程序较小,缺点是控件相对较少. wxPython 基于wxWidg ...
- HDU2063 过山车(二分匹配)
过山车 HDU - 2063 RPG girls今天和大家一起去游乐场玩,终于可以坐上梦寐以求的过山车了.可是,过山车的每一排只有两个座位,而且还有条不成文的规矩,就是每个女生必须找个个男生做part ...
- PHP的zip压缩工具扩展包学习
总算到了 PHP 的拿手好戏上场了,前面我们学习过 Bzip2 . LZF . Phar 和 rar 这些压缩相关扩展在 PHP 中的使用,不过它们要么是太冷门,要么就是很多功能不支持.而 Zip 则 ...
- Shell系列(30)- 单分支if语句判断分区使用率
需求 监控分区已用空间,超过80%,抛出警告 脚本 #!/bin/bash #给tets赋值用于接收参数,传递给if进行判断 #申明变量test并赋值,由于赋的值是系统变量的结果,所以得用$()引用 ...
- fibnacci数列
斐波那契数列(Fibonacci sequence),又称黄金分割数列.因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为"兔子数列&qu ...
- disruptor笔记之三:环形队列的基础操作(不用Disruptor类)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- abp 以或的方式验证多个 AuthorizeAttribute
前言 在使用 abp 开发业务功能时,会遇到公用同一个类的情况,在给这个类配置权限时,就要添加多个 AuthorizeAttribute,类似下面这样: [Authorize(DcsPermissio ...
- 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 百篇博客分析OpenHarmony源码 | v36.04
百篇博客系列篇.本篇为: v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CP ...