我们经常使用消息队列进行系统之间的解耦,日志记录等等。但是有时候我们在使用 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处理未被路由的消息的更多相关文章

  1. RabbitMQ(4) 未路由的消息、TTL和死信

    未路由的消息 当生产这发送的消息到达指定的交换器后,如果交换器无法根据自身类型.绑定的队列以及消息的路由键找到匹配的队列,默认情况下消息将被丢弃.可以通过两种方式 处理这种情况,一是在发送是设置man ...

  2. RabbitMQ不讲武德,发个消息也这么多花招

    前言 本篇博客已被收录GitHub:https://zhouwenxing.github.io/ 文中所涉及的源码也已被收录GitHub:https://github.com/zhouwenxing/ ...

  3. RabbitMQ系列(四)RabbitMQ事务和Confirm发送方消息确认——深入解读

    RabbitMQ事务和Confirm发送方消息确认--深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...

  4. RabbitMQ详解(二)------消息通信的概念

    PS:近期在南宁出差,工作比较忙,所以更新会比较慢. 说到消息通信,可能我们首先会想到的是邮箱,QQ,微信,短信等等这些通信方式,这些通信方式都有发送者,接收者,还有一个中间存储离线消息的容器.但是这 ...

  5. RabbitMQ-消费者"未处理完的消息"丢失

    一个关于客户端(消费者)开启自动应答,重启后"未处理消息丢失"的小坑.(主要是对RabbitMQ理解不够) 首先,申明一下: 本文所谓的 "丢失消息" 不是指服 ...

  6. RabbitMQ六种队列模式-路由模式

    前言 RabbitMQ六种队列模式-简单队列RabbitMQ六种队列模式-工作队列RabbitMQ六种队列模式-发布订阅RabbitMQ六种队列模式-路由模式 [本文]RabbitMQ六种队列模式-主 ...

  7. RabbitMQ入门教程(十二):消息确认Ack

    原文:RabbitMQ入门教程(十二):消息确认Ack 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csd ...

  8. RabbitMQ事务和Confirm发送方消息确认

    RabbitMQ事务和Confirm发送方消息确认——深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...

  9. 一个基于RabbitMQ的可复用的事务消息方案

    前提 分布式事务是微服务实践中一个比较棘手的问题,在笔者所实施的微服务实践方案中,都采用了折中或者规避强一致性的方案.参考Ebay多年前提出的本地消息表方案,基于RabbitMQ和MySQL(JDBC ...

随机推荐

  1. Intel® QAT加速卡之性能简介

    Intel QuickAssist Adapter 8950 设备简介 支持英特尔QuickAssist技术的英特尔QuickAssist适配器提供加密加速和压缩加速服务. 1. Key featur ...

  2. Java匿名内部类只可使用一次的理解

    匿名内部类只能使用一次,就不能在使用. 为了验证"只能使用一次"的实际含义,首先在程序中实例化了两个对象.并且在相应的类中加入了实现接口的匿名内部类: package com.An ...

  3. java版gRPC实战之三:服务端流

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  4. docker学习笔记(二)--配置镜像加速器

    前提:docker已经安装好 配置过程 进入至阿里云开发中心,https://dev.aliyun.com/,点击管理中心 管理中心中,点击左侧镜像加速器. 修改配置文件,使用加速器,根据我们目前Do ...

  5. 苹果ASA广告投放归因的接入

    前段时间,苹果终于在大陆区开放了应用商店的竞价广告.毫无疑问又开启了苹果应用导量的新玩法,各大厂商都紧跟脚步吃螃蟹.本篇讲解苹果广告中的归因部分. 苹果广告其实在海外已运行多年,而因为IDFA的政策变 ...

  6. js 获取转换网址中文参数

    var search = decodeURI(location.search).substr(1); console.log(search); decodeURI 方法返回一个已编码的统一资源标识符 ...

  7. Jmeter系列(15)- 常用断言之大小断言

    大小断言 大小断言验证响应数据size大小,它的作用范围有主Sample与子Sample:适用场景,判断附件下载的大小,比如项目安装包 完整响应:全部响应信息 响应头:响应头信息,比如http协议的头 ...

  8. Nginx系列(9)- Nginx常用命令

    Linux # 命令需要在Nginx的sbin目录下执行 cd /usr/local/nginx/sbin/ ./nginx #启动./nginx -s stop #停止 ./nginx -s qui ...

  9. photoshop CS6打不开提示请卸载并重新安装该程序

    运行Photoshop.exe时,提示错误: 解决方案: 以管理员身份运行该exe文件

  10. SVN服务器的环境搭建(图文并茂)

    SVN服务器的搭建与使用 本章读完,就可以创建代码版本库,传送门 https://www.cnblogs.com/zzpython/p/13348009.html TortoiseSVN 是 Subv ...