接续之前文章
Springboot2.2.6构建RabbitMQ消息接收端
小T说:消息中间件,构建RabbitMQ消息发布端代码
大家好,我是技术人小Top
今天咱们来介绍如何使用RabbitMQ构建消息异常处理 ^-^
![5442668d39da4b0b93bc23dfdefa4292.png](https://i-blog.csdnimg.cn/blog_migrate/3beabb782b9d9a2316ff3a5075d31d46.jpeg)
RabbitMQ官网:www.rabbitmq.com
上次介绍了Spring2.26如何构建RabbitMQ消息接收端
具体到应用开发,需要使用RabbitMQ API来实现具体业务场景
现在开始进入实战
消息是否落地保存
建议:落地保存
1、落地保存的好处
- 消息保存在数据库中方便人工和程序处理
虽然RabbitMQ具备消息持久化的特性,但是它毕竟不是存储设备。相比较数据库而言,数据的可读性、易用性和稳定性都要逊色。
- 事后方便业务对账
通常消息发送和接收确认后,RabbitMQ中不再持久化消息了。这时一旦业务对账出现了问题,消息不存在则无法追溯,无法对异常情况进行复位排差。
2、落地保存的具体位置/时机
详见本号之前的Springboot2.2.6构建RabbitMQ消息发布端代码、Springboot2.2.6构建RabbitMQ消息接收端代码文章
- 发送消息到Exchange成功时
- 发送消息到Exchange失败时
- 发送消息到Queue失败时
- 接收消息成功时
- 接收消息失败时
消息是否设置过期
根据实际需要来决定
1、设置过期时间的好处
- 消息及时释放/删除会减轻RabbitMQ对磁盘占用的压力
从业务角度来说,消息的持久化毕竟是短暂的或瞬态的,不需要像数据库那样永久保存数据
- 即使出现异常也不会对RabbitMQ造成磁盘压力
只要配合消息落地,就算有异常产生,在设计层面可以依赖数据库保证数据的永久可追溯
2、设置过期时间的实现
- 对Queue或Message都可设置过期时间
大多数场景会对Queue设置过期时间。当同时设置过期时间时,以最小的过期时间为准
Queue userQueue1 = QueueBuilder.durable(MQ_QUEUE_USER1) //Todo: ttl()方法用于设置Queue的过期时间 .ttl(300000).build();
消息是否设置死信队列
根据实际需要来决定
1、设置死信队列的好处
- 保证消费的队列没有垃圾数据
由于通常消息是持久化的,原队列也就是业务队列中的数据会不断进入新的消息。这时如果存在异常消息(也就是消费端处理过程中发生异常而无法处理)长时间存放在原队列是没有意义的。因为这通常需要消费端排查问题并修复代码。
- 保证消费的队列高效运行
由于没有死信(处理不了的异常消息),队列消费的速度会很高效。反之,如果重复入队,效率会降低。如果不重复入队,需要有追溯机制来跟进和解决异常问题。
- 便于保存异常消息并定位问题
2、设置死信队列的实现
之前的AMQP协议、模型及RabbitMQ常用组件文章中发过这样一张图,说明了AMQP的工作原理
![baad673f715cfe77e2088dfd07683ce6.png](https://i-blog.csdnimg.cn/blog_migrate/a4d9753a482f2d1e9d8ff0c2ba51a92f.jpeg)
在此图基础上,我们把死信交换机和死信队列加上,一张图就能看明白了(请关注红色箭头)
1、当**Consumer1**从**Queue**(user1)中接收消息失败时,调用API告诉RabbitMQ抛弃错误消息
2、由于**Queue**(user1)设置了**死信交换机**,被抛弃的消息成为死信进入死信交换机user1DLXExchange
3、由于**Queue**(user1DLX)绑定了**死信交换机**,死信队列会接收到被抛弃的消息
![9e48a1f907625a0ff1521bc1aa46c45e.png](https://i-blog.csdnimg.cn/blog_migrate/d2739ca5003d8e82707f67895dbbed96.jpeg)
我们将之前文章中的代码稍加改造就可以了
//Todo: 增加死信交换机和死信队列的命名/** * 死信交换机user1名称 */public static final String MQ_DLX_EXCHANGE_USER1 = "user1DLXExchange";/** * 死信交换机user2名称 */public static final String MQ_DLX_EXCHANGE_USER2 = "user2DLXExchange";/** * 死信队列user1名称 */public static final String MQ_DLX_QUEUE_USER1 = "user1DLX";/** * 死信队列user2名称 */public static final String MQ_DLX_QUEUE_USER2 = "user2DLX";@Beanpublic Declarables declarables() { //Todo: user1指定死信交换机deadLetterExchange Queue userQueue1 = QueueBuilder .durable(MQ_QUEUE_USER1) .deadLetterExchange(MQ_DLX_EXCHANGE_USER1) .build(); //Todo: user2指定死信交换机deadLetterExchange Queue userQueue2 = QueueBuilder .durable(MQ_QUEUE_USER2) .deadLetterExchange(MQ_DLX_EXCHANGE_USER2) .build(); FanoutExchange fanoutExchange = ExchangeBuilder. fanoutExchange(MQ_FANOUT_EXCHANGE_USER) .durable(true) .build(); DirectExchange directExchange = ExchangeBuilder .directExchange(MQ_FANOUT_EXCHANGE_TEST) .durable(true) .build(); //Todo: 定义死信队列 Queue userDLXQueue1 = QueueBuilder .durable(MQ_DLX_QUEUE_USER1) .ttl(300000) .build(); Queue userDLXQueue2 = QueueBuilder .durable(MQ_DLX_QUEUE_USER2) .ttl(300000) .build(); //Todo: 定义死信队列交换机 FanoutExchange dlxUser1Exchange = ExchangeBuilder .fanoutExchange(MQ_DLX_EXCHANGE_USER1) .durable(true) .build(); FanoutExchange dlxUser2Exchange = ExchangeBuilder .fanoutExchange(MQ_DLX_EXCHANGE_USER2) .durable(true) .build(); return new Declarables(userQueue1, userQueue2, fanoutExchange, directExchange, userDLXQueue1, userDLXQueue2, dlxUser1Exchange, dlxUser2Exchange, BindingBuilder.bind(userQueue1).to(fanoutExchange), BindingBuilder.bind(userQueue2).to(fanoutExchange), BindingBuilder.bind(userDLXQueue1).to(dlxUser1Exchange), BindingBuilder.bind(userDLXQueue2).to(dlxUser2Exchange));}
小结
今天主要介绍了如何构建RabbitMQ异常处理
小伙伴们都了解了吗?
下次小Top将继续介绍RabbitMQ开发
对于今天的内容有任何疑问或问题,欢迎留言或讨论 ^-^
本文由星空奇点原创,欢迎关注,带你一起长知识!