我们经常使用消息队列进行系统之间的解耦,日志记录等等。但是有时候我们在使用 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. openswan协商流程之(四):main_inI2_outR2()

    主模式第四包:main_inI2_outR2 1. 序言 main_inI2_outR2()函数是ISAKMP协商过程中第四包的核心处理函数的入口,同时在此处理流程中已经获取到足够的隧道信息,可以生成 ...

  2. 第06课:GDB 常用命令详解(中)

    本课的核心内容: info 和 thread 命令 next.step.util.finish.return 和 jump 命令 info 和 thread 命令 在前面使用 info break 命 ...

  3. Swagger-初见

    目录 Swagger简介 SpringBoot集成Swagger 配置Swagger 配置扫描接口 配置Swagger开关 配置API分组 实体配置 常用注解 Swagger简介 前后端分离 前端 - ...

  4. 记录一次sql注入绕过

    目标:http://www.xxxxx.net/temp.asp?ID=10359 通过 and 1=1 and 1=2 测试发现存在拦截 首先想到 and 空格 = 可能存在触发规则 一般遇到这种情 ...

  5. 内部类访问外部类成员变量,使用外部类名.this.成员变量

    public class Outer { private int age = 12; class Inner { private int age = 13; public void print() { ...

  6. 10.6Java学习

    1.类,对象,方法的定义.2.标识符分为两类:关键字/常见的基本类型:boolean(布尔型),byte(字节型),char(字符型),double(双精度),float(浮点),int(整型),lo ...

  7. 关于pycharm创建django1.x和3.x项目的说明

    1.我创建了两个模板文件分别代表django1.x和3.x 2.两个模板文件分别为Django1Template和Django3Template (不同模板文件中存放不同的django版本) 3.使用 ...

  8. 让PHP能够调用C的函数-FFI扩展

    在大型公司中,一般会有很我编程语言的配合.比如说让 Java 来做微服务层,用 C++ 来进行底层运算,用 PHP 来做中间层,最后使用 JS 展现效果.这些语言间的配合大部分都是通过 RPC 来完成 ...

  9. Jmeter系列(1) - 踩坑之代理服务器录制失败

    前景 Jmeter代理服务器报错信息如下.Jmeter录制不成功 解决方案 需了解 代理服务器启动后会在/bin目录生成ApacheJMeterTemporaryRootCA.crt和ApacheJM ...

  10. web自动化:IE11运行Python+selenium程序

    from selenium import webdriver # 运行此脚本前必须按要求修改注册表'''[HKEY_CURRENT_USER\Software\Microsoft\Internet E ...