我们经常使用消息队列进行系统之间的解耦,日志记录等等。但是有时候我们在使用 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. epoll代码框架

    epoll代码实现框架: #define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS]; int listen_sock, conn_ ...

  2. K8S 简介

    K8S架构与组件 kubectl: 是一个客户端管理工具,直接管理API server,提供请求给API server,中间有auth认证.用户使用kubectl命令来请求API Server接口完成 ...

  3. JDBC管理事务

    一.事务概念:打包一起的多个步骤的业务操作,要么同事成功,要么同时失败,则需要用事务管理: 二.代码实现

  4. 手把手教你 Docker Compose安装DOClever

    一.什么是Docker Compose以及Docker Compose的安装和使用 查看我的另外一篇博客:Docker Compose的安装和使用 二.DOClever是什么 DOClever是一个可 ...

  5. redis连接密码和指定数据库

    台服务器上都快开启200个redis实例了,看着就崩溃了.这么做无非就是想让不同类型的数据属于不同的应用程序而彼此分开. 那么,redis有没有什么方法使不同的应用程序数据彼此分开同时又存储在相同的实 ...

  6. sqlite3 import/export db sqlite 导入 导出 数据

    export: $ sqlite3 xxx.db3 > .output xxx.sql >.dump > .q import: $ sqlite3 xxx.db3 > .rea ...

  7. phpspider PHP 爬虫

    * 通过composer下载 composer require owner888/phpspider // composer.json { "require": { "o ...

  8. Appium调试分析方法

    在使用appium做自动化测试的时候,发现用例报错,如何排查原因? 查看appium日志 appium日志大概是分为以下部分 culr命令调试 在理解appium协议的基础上,可以直接用shell发送 ...

  9. postgresql批量插入copy_from()的使用

    在批量插入postgresql时想使用同Mysql的语法时发现并不能使用: cursor.executemany("INSERT INTO persons VALUES (%d, %s, % ...

  10. P4929-[模板]舞蹈链(DLX)

    正题 题目链接:https://www.luogu.com.cn/problem/P4929 题目大意 \(n*m\)的矩形有\(0/1\),要求选出若干行使得每一列有且仅有一个\(1\). 解题思路 ...