Spring Boot系列——死信队列
在说死信队列之前,我们先介绍下为什么需要用死信队列。
如果想直接了解死信对接,直接跳入下文的"死信队列"部分即可。
ack机制和requeue-rejected属性
我们还是基于上篇《Spring Boot系列——7步集成RabbitMQ》的demo代码来说。
在项目springboot-demo我们看到application.yaml文件部分配置内容如下
...
listener:
    type: simple
    simple:
      acknowledge-mode: auto
      concurrency: 5
      default-requeue-rejected: true
      max-concurrency: 100
...
其中
acknowledge-mode
该配置项是用来表示消息确认方式,其有三种配置方式,分别是none、manual和auto。
none意味着没有任何的应答会被发送。
manual意味着监听者必须通过调用Channel.basicAck()来告知所有的消息。
auto意味着容器会自动应答,除非MessageListener抛出异常,这是默认配置方式。
default-requeue-rejected
该配置项是决定由于监听器抛出异常而拒绝的消息是否被重新放回队列。默认值为true。
我一开始对于这个属性有个误解,我以为rejected是表示拒绝,所以将requeue-rejected连起来是拒绝重新放回队列,后来查了资料明白这个属性的功能才想起来rejected是个形容词,其表示的应该是被拒绝的消息
所以如果该属性配置为true表示会重新放回队列,如果配置为false表示不会放回队列。
下面我们看看acknowledge-mode参数和default-requeue-rejected参数使用不同的组合方式,RabbitMQ是如何处理消息的。
代码依然使用springboot-demo中的RabbitApplicationTests发送消息,使用Receiver类监听demo-queue队列的消息。
对于Receiver类添加了一行代码,该代码模拟抛出异常
@Component
public class Receiver {
    @RabbitListener(queues = "demo_queue")
    public void created(String message) {
        System.out.println("orignal message: " + message);
        int i = 1/0;
    }
}
acknowledge-mode=none, default-requeue-rejected=false

该配置不会确认消息是否正常消费,所以在控制台没有抛出任何异常。通过在RabbitMQ管理页面也没有看到重新放回队列的消息
acknowledge-mode=none, default-requeue-rejected=true

同样该配置不会确认消息是否正常消费,所以在控制台没有抛出任何异常。而且即使default-requeue-rejected配置为true因为没有确认所以也没有看到重新放回队列的消息
acknowledge-mode=manual, default-requeue-rejected=false

该配置需要手动确认消息是否正常消费,但是代码中并没有手动确认,个人理解是因为没有收到ack,所以消息又回到了队列中。
acknowledge-mode=manual, default-requeue-rejected=true

该配置需要手动确认消息是否正常消费,但是代码中并没有手动确认,所以消息被重新放入到队列中了,并且在控制台发现还抛出了异常(这块不是很清楚,default-requeue-rejected设置true和false带来的不同效果,有了解的麻烦下方留言指教)。
acknowledge-mode=auto, default-requeue-rejected=false

该配置采用自动确认,从结果来看,是自动确认了。
从控制台打印的结果可以看出Receiver方法执行了3次,分别是前面两条放回队列的消息以及这次发送的消息,所以3条消息都消费了。
同时因为default-requeue-rejected设置为false,所以即使消费抛出异常,也没有将消息放回队列。
acknowledge-mode=auto, default-requeue-rejected=true

该配置同样采用自动确认,从结果看出,没有抛出异常(这块也不是很理解),且因为default-requeue-rejected设置为true,所以消息重新回到队列。
综上罗列这么多情况只为说明有些情况下,如果消息消费出错,因为配置问题导致消息丢失了。这在很多情况下是要命的,比如用户支付的订单号,如果因为抛异常等原因直接丢失是很要命的。
所以,我们需要有一个确保机制,能够保证即使失败的消息也能保存下来,这时候死信队列就排上用场了。
死信队列
死信队列的整个设计思路是这样的
生产者 --> 消息 --> 交换机 --> 队列 --> 变成死信 --> DLX交换机 -->队列 --> 消费者
下面我们通过网上的一个简单的死信队列的实现看看如何使用死信队列。
@Bean("deadLetterExchange")
    public Exchange deadLetterExchange() {
        return ExchangeBuilder.directExchange("DL_EXCHANGE").durable(true).build();
    }
    @Bean("deadLetterQueue")
    public Queue deadLetterQueue() {
        Map<String, Object> args = new HashMap<>(2);
//       x-dead-letter-exchange    声明  死信交换机
        args.put("x-dead-letter-exchange", "DL_EXCHANGE");
//       x-dead-letter-routing-key    声明 死信路由键
        args.put("x-dead-letter-routing-key", "KEY_R");
        return QueueBuilder.durable("DL_QUEUE").withArguments(args).build();
    }
    @Bean("redirectQueue")
    public Queue redirectQueue() {
        return QueueBuilder.durable("REDIRECT_QUEUE").build();
    }
    /**
     * 死信路由通过 DL_KEY 绑定键绑定到死信队列上.
     *
     * @return the binding
     */
    @Bean
    public Binding deadLetterBinding() {
        return new Binding("DL_QUEUE", Binding.DestinationType.QUEUE, "DL_EXCHANGE", "DL_KEY", null);
    }
    /**
     * 死信路由通过 KEY_R 绑定键绑定到死信队列上.
     *
     * @return the binding
     */
    @Bean
    public Binding redirectBinding() {
        return new Binding("REDIRECT_QUEUE", Binding.DestinationType.QUEUE, "DL_EXCHANGE", "KEY_R", null);
    }
注意
- 声明了一个direct模式的exchange。 
- 声明了一个死信队列deadLetterQueue,该队列配置了一些属性 - x-dead-letter-exchange表明死信交换机,- x-dead-letter-routing-key表明死信路由键,因为是direct模式,所以需要设置这个路由键。
- 声明了一个替补队列redirectQueue,变成死信的消息最终就是存放在这个队列的。 
- 声明绑定关系,分别是死信队列以及替补队列和交换机的绑定。 
那么如何模拟生成一个死信消息呢,可以在发送到DL_QUEUE的消息在10秒后失效,然后转发到替补队列中,代码实现如下
public void sendMsg(String content) {
        CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
        MessagePostProcessor messagePostProcessor = message -> {
            MessageProperties messageProperties = message.getMessageProperties();
//            设置编码
            messageProperties.setContentEncoding("utf-8");
//            设置过期时间10*1000毫秒
            messageProperties.setExpiration("5000");
            return message;
        };
        rabbitTemplate.convertAndSend("DL_EXCHANGE", "DL_KEY", content, messagePostProcessor);
    }
执行结果如下

消息首先进入DL_QUEUE,5秒后失效,被转发到REDIRECT_QUEUE中。
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

Spring Boot系列——死信队列的更多相关文章
- Spring Boot 系列总目录
		一.Spring Boot 系列诞生原因 上学那会主要学的是 Java 和 .Net 两种语言,当时对于语言分类这事儿没什么概念,恰好在2009年毕业那会阴差阳错的先找到了 .Net 的工作,此后就开 ... 
- Spring Boot 系列教程19-后台验证-Hibernate Validation
		后台验证 开发项目过程中,后台在很多地方需要进行校验操作,比如:前台表单提交,调用系统接口,数据传输等.而现在多数项目都采用MVC分层式设计,每层都需要进行相应地校验. 针对这个问题, JCP 出台一 ... 
- Spring Boot 系列教程18-itext导出pdf下载
		Java操作pdf框架 iText是一个能够快速产生PDF文件的java类库.iText的java类对于那些要产生包含文本,表格,图形的只读文档是很有用的.它的类库尤其与java Servlet有很好 ... 
- Spring Boot 系列教程17-Cache-缓存
		缓存 缓存就是数据交换的缓冲区(称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找.由于缓存的运行速度比内存快得多,故缓存的作用就是帮 ... 
- Spring Boot 系列教程16-数据国际化
		internationalization(i18n) 国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式. 它要求从产品中抽离所有地域语言,国家/地区和 ... 
- Spring Boot 系列教程15-页面国际化
		internationalization(i18n) 国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式. 它要求从产品中抽离所有地域语言,国家/地区和 ... 
- Spring Boot 系列教程14-动态修改定时任务cron参数
		动态修改定时任务cron参数 不需要重启应用就可以动态的改变Cron表达式的值 不能使用@Scheduled(cron = "${jobs.cron}")实现 DynamicSch ... 
- Spring Boot 系列教程12-EasyPoi导出Excel下载
		Java操作excel框架 Java Excel俗称jxl,可以读取Excel文件的内容.创建新的Excel文件.更新已经存在的Excel文件,现在基本没有更新了 http://jxl.sourcef ... 
- Spring Boot 系列教程11-html页面解析-jsoup
		需求 需要对一个页面进行数据抓取,并导出doc文档 html解析器 jsoup 可直接解析某个URL地址.HTML文本内容.它提供了一套非常省力的API,可通过DOM,CSS以及类似于JQuery的操 ... 
随机推荐
- hdu3944
			hdu3944题目中给出的杨辉三角形的形状带有误导目的,应该转化成对称的形状再去思考这个问题分两种情况第一个是在左区从目标位置向左上方走一直走到边界,然后再向右上方一直走到起点n-k个1加上C(n-k ... 
- 真正“搞”懂http协议01—背景故事
			去年读了<图解HTTP>.<图解TCP/IP>以及<图解网络硬件>但是读了之后并没有什么深刻的印象,只是有了一层模糊的脉络,刚好最近又接触了一些有关http的相关内 ... 
- Django 学习第七天——Django模型基础第二节
			User 是自己创建的模型类,等于数据库中的表 常用的查询方法: all():获取所有数据: User.objects.all() first():获取第一条数据: User.objects.firs ... 
- codeforces651----A. Joysticks
			//贪心,注意特判即可 #include <iostream> using namespace std; int main() { ; cin >> a >> b; ... 
- 2017-9-11-Linux开机启动脚本
			参考文章:https://www.magentonotes.com/ubuntu-config-autostart-shell-script.html 还是先开门见山的说,Linux需要添加开机启动程 ... 
- 屏幕录制软件camtasia studio 8序列号激活
			注册名:TEAM MESMERiZE序列号:3MHCA-5DMCV-H89T8-V8GML-W6FB8 打开hosts文件:C:\Windows\System32\drivers\etc\hosts把 ... 
- BZOJ1423 : Optimus Prime
			设$f[x]$表示为了保证自己可以取到质数$x$,第一步在$[0,n]$中可以选的数是多少. 这个数是唯一的,因为如果存在两个$f[x]=a,b(a<b)$,那么如果先手取了$a$,后手就能取$ ... 
- [jzoj]3777.最短路(shortest)
			Link https://jzoj.net/senior/#main/show/3777 Description 小Y最近学得了最短路算法,一直想找个机会好好练习一下.话虽这么说,OJ上最短路的题目都 ... 
- 一个label两种颜色,一个label两种字体
			-(void)addLabel{ UILabel *label = [[UILabel alloc]init]; label.backgroundColor = [UIColor grayColor] ... 
- sklearn神经网络分类
			sklearn神经网络分类 神经网络学习能力强大,在数据量足够,隐藏层足够多的情况下,理论上可以拟合出任何方程. 理论部分 sklearn提供的神经网络算法有三个: neural_network.Be ... 
