消息可靠性

确保消息至少被消费了一次(不丢失)

消息丢失的几种情况:

  1. 消息在网络传输时丢失,如生产者到交换机,交换机到队列的过程中
  2. MQ宕机:消息到达Queue了,但在消费者消费之前,MQ就宕机了
  3. 消费者宕机:消费者接收了消息但是还没处理就宕机了

如何解决? RabbitMQ分别针对生产者, MQ和消费者这三个角色提供了一些解决方法

生产者消息确认

根据下图,消息的传输丢失可能有3种情况:

  • 消息可能在从生产者到exchange的时候丢失,即没到达exchange。
  • 消息从exchange通过routingKey将消息路由到queue 时丢失,即没到达queue
  • 消息成功到达queue后,没有被消费者消费或者消费者获取消息还没经过处理就宕机了

Publisher Confirms

Since AMQP gives few guarantees regarding message persistence/handling, the traditional way to do this is with transactions, which can be unacceptably slow. To remedy this problem, we introduce an extension to AMQP in the form of Lightweight Publisher Confirms. (渣翻:传统的方式是采用事务控制,但这种方法十分地慢,RabbitMQ提供了一种更加轻量级的方式:生产者确认。

  • RabbitMq发送到消费者的消息确认
  • RabbitMQ到生产者的消息确认 (这种机制是Rabbitmq对Amqp协议的扩展)

    以上两种特性都收到了TCP协议的启发

它们对于从发布者到 RabbitMQ 节点以及从 RabbitMQ 节点到消费者的可靠交付都是必不可少的。换句话说,它们对于数据安全至关重要,应用程序与 RabbitMQ 节点一样负责。

示例

Spring-amqp支持 publish confirm and returns 的 RabbitTemplate实现。 我们将使用springboot+rabbitmq来模拟上面三种情况。

RabbitTemplate 是spring-amqp定义的一个模板,它实现了AmqpTemplate接口(此接口定义了涵盖发送和接收消息的一般行为。)

  1. 新建工程,包含两个模块:consumer和publisher

配置文件:

  1. # publisher的配置文件: (consumer的配置文件差不多)
  2. logging:
  3. pattern:
  4. dateformat: HH:mm:ss:SSS
  5. level:
  6. cn.itcast: debug
  7. spring:
  8. rabbitmq:
  9. host: 192.168.57.100 # rabbitMQ的ip地址
  10. port: 5672 # 端口
  11. username: xxx # 用户名称
  12. password: 1234
  13. virtual-host: /
  14. publisher-confirm-type: correlated # 指定生产者确认的模式,correlated表示异步
  15. publisher-returns: true # 开启生产者返回
  16. template: # rabbitTemplage的设置,也可以通过代码配置
  17. mandatory: true # 如果设置为true,发送失败的消息会被ReturnCallBack方法回调

Publish配置类,通过RabbitTemplate配置ReturnCallback函数用来处理消息没到队列的情况。

根据spring-amqp文档,一个RabbitTemplate只能支持一个ReturnCallback 函数。


  1. @Slf4j
  2. @Configuration
  3. public class CommonConfig implements ApplicationContextAware{
  4. @Override
  5. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  6. // 获取rabbitTemplate对象
  7. RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
  8. rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) ->
  9. log.error("消息发送队列失败:{},响应码:{},失败原因:{},交换机:{},routingKey:{}",
  10. message, replyCode, replyText, exchange, routingKey));
  11. //重发消息...
  12. }
  13. }

这样我们就完成了一种失败处理:消息无法路由到队列。

测试类:

  1. @Slf4j
  2. @RunWith(SpringRunner.class)
  3. @SpringBootTest
  4. public class SpringAmqpTest {
  5. @Autowired
  6. private RabbitTemplate rabbitTemplate;
  7. @Test
  8. public void testSendMessage2SimpleQueue() throws InterruptedException {
  9. String routingKey = "simple.test";
  10. String message = "hello spring-amqp!";
  11. //准备CorrelationData,它包含了一个全局的ID用来标识消息,并且设置了两个函数用于处理其余两种错误
  12. CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
  13. correlationData.getFuture().addCallback(confirm -> {
  14. if(confirm.isAck()){
  15. log.debug("消息成功投递到交换机!消息ID:{}",correlationData.getId());
  16. }else{
  17. log.error("消息投递到交换机失败,消息ID:{}",correlationData.getId());
  18. }
  19. }, throwable -> {
  20. //失败,记录日志
  21. log.error("消息发送失败 ",throwable) ;
  22. });
  23. rabbitTemplate.convertAndSend("amq.topic",routingKey,message,correlationData);
  24. }

在测试中:

  • 如果我们将routingKey改成MQ中不存在值,则会触发 ReturnCallback回调函数, 因为消息到达交换机后无法路由到任何一个队列
  • 同样的,如果我们将交换机amq.topic设置成MQ中不存在的值,则会得到 nack publish confirm, 因为消息无法投递到交换机。

消费者消息确认

消息几经波折终于到达了消费者这里,结果消费者还没来得及处理就宕机了,或在处理过程中发生了异常,导致消息没有被正确处理。而RabbitMQ认为消费者已经消费了,直接把消息丢掉。那前面的工作都白忙了。

于是,RabbitMQ提供了消费者确认机制。消费者处理消息后向MQ发送ack回执,MQ收到ack才会丢弃消息

Spring-amqp则允许配置三种确认模式:

  • manual:手动ack,业务处理完成后,由程序员调用api发送ack
  • auto:自动ack,由spring监测listen代码是否出现异常,没有异常则返回ack,抛出异常则返回nack
  • none: 关闭ack,MQ假定消费者拿到消息后会成功处理,因此消息投递后会马上删除

示例

consumer配置文件yaml中指定spring-amqp的确认模式,这里以auto来演示,并开启失败重传。

  1. logging:
  2. pattern:
  3. dateformat: HH:mm:ss:SSS
  4. level:
  5. cn.itcast: debug
  6. spring:
  7. rabbitmq:
  8. host: 192.168.57.100 # rabbitMQ的ip地址
  9. port: 5672 # 端口
  10. username: wingdd
  11. password: 1234
  12. virtual-host: /
  13. listener:
  14. simple:
  15. prefetch: 1
  16. acknowledge-mode: auto # 开启自动确认模式

消费者发生异常后,消息会重新入队requeue到队列,再发送给消费者,这会导致mq压力过大。因此,我们需要利用spring的retry机制,消费者异常时进行本地重试,而不是无限制的入队。


增加重传的配置:

  1. retry:
  2. enabled: true # 开启重传
  3. initial-interval: 1000ms # 初始失败等待时长
  4. multiplier: 3 # 下一次等待时长的倍数,下一次等待时长=multiplier * last-interval
  5. max-attempts: 3 # 最大重传次数
  6. stateless: true # 无状态,默认为true。 如果业务中有事务,则要改成false
  7. max-interval: 10000ms # 最大等待时长,约束multiplier和interval
  1. @Slf4j
  2. @Component
  3. public class SpringRabbitListener {
  4. @RabbitListener(queues="simple.queue")
  5. public void ListenSimpleQueue(String msg) {
  6. System.out.println("消费者接收到来自simple.queue的消息:【 " + msg + " 】");
  7. int a= 1/0 ; //将抛出异常
  8. log.info("消费者处理成功") ;
  9. }
  10. }

死信交换机

死信交换机就是指接收死信的交换机。

什么样的消息会称为死信?

  • 被消费者使用basic.reject或者basic.nack返回,并且requeue参数是false的消息。
  • TTL过期未被消费的消息
  • 队列满了导致被丢弃了的消息

TTL,Time-To-Live,如果一个队列的消息TTL结束仍未消费,则会变成死信,ttl超时分为两种情况:1. 消息所在的队列设置了存活时间 2.消息本身设置了存活时间。( 如果两者都有设置,选短的那个)

使用死信交换机和TTL,可以实现消费者延迟接收消息的效果,这种消息模式乘坐延迟队列(Delay Queue)模式

延迟队列的使用场景包括:

  • 延迟发送短信
  • 用户下单后15分钟未支付则自动取消
  • 预约工作会议,20分钟后自动通知参会人员。

例子

如图,我们将实现这样的场景,publish将消息发送到ttl交换机再到ttl Queue后,消息超时将被投递到死信交换机和队列,此时consumer再消费消息,这就达到了延迟消息的效果



①、在consumer中添加 监听dl.queue

  1. @RabbitListener(bindings= @QueueBinding(
  2. value=@Queue(name="dl.queue",durable = "true"),
  3. exchange=@Exchange(name="dl.direct"),
  4. key="dl"
  5. ))
  6. public void listenDlQueue(String msg){
  7. log.info("消费者接收到了dl.queue的消息 【"+msg+"】");
  8. }

②、comsuer增加配置类用于创建ttl.queue和ttl交换机,并绑定。

  1. package cn.itcast.mq.config;
  2. import org.springframework.amqp.core.*;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. @Configuration
  6. public class TTLConfiguration {
  7. @Bean
  8. public DirectExchange ttlExchange(){
  9. return new DirectExchange("ttl.direct") ;
  10. }
  11. @Bean
  12. public Queue ttlQueue(){
  13. return QueueBuilder.durable("ttl.queue").
  14. ttl(6000).
  15. deadLetterExchange("dl.direct").
  16. deadLetterRoutingKey("dl").
  17. build();
  18. }
  19. // 将ttl交换机和ttlQueue绑定起来
  20. @Bean
  21. public Binding dlbinding(){
  22. return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("ttl");
  23. }
  24. }

③、在publisher模块中向ttl.queue发送消息

  1. @Test
  2. public void TTLTest() {
  3. Message message = MessageBuilder.withBody("hello TTL Queue Or Dead Queue!".getBytes(StandardCharsets.UTF_8))
  4. .build();
  5. rabbitTemplate.convertAndSend("ttl.direct", "ttl",message) ;
  6. log.info("消息发送成功了!");
  7. }

结果:消费者最终接收到了dl.queue的消息

高可用问题

从单点MQ到集群MQ

消息堆积问题

当生产者发送消息的速度超过了消费者处理消息的速度,就会导致队列中的消息堆积,直到队列存储消息达到上限,此时

最早接收到的消息就会变成死信,被丢弃。 这就是消息堆积问题

解决消息堆积的思路:

  1. 增加更多的消费者,提高消费速度
  2. 在消费者内开启线程池加快消息处理速度
  3. 扩大队列容积,提高堆积上限

惰性队列

RabbitMQ从3.6.0版本开始就增加了Lazy Queues的概念,惰性队列的特征如下:

  • 接收到消息后直接存入磁盘而非内存
  • 消费者要消费消息时才会从磁盘中读取并加载到内存
  • 支持数百万条的消息存储

参考

代码:根据Bilibili黑马教程编写

https://www.cnblogs.com/binghe001/p/14443360.html

https://docs.spring.io/spring-amqp/docs/1.6.3.RELEASE/reference/html/_reference.html

RabbitMQ消息可靠性、死信交换机、消息堆积问题的更多相关文章

  1. SpringBoot整合RabbitMQ实战附加死信交换机

    前言 使用springboot,实现以下功能,有两个队列1.2,往里面发送消息,如果处理失败发生异常,可以重试3次,重试3次均失败,那么就将消息发送到死信队列进行统一处理,例如记录数据库.报警等 环境 ...

  2. Rabbitmq消费失败死信队列

    Rabbitmq 重消费处理 一 处理流程图: 业务交换机:正常接收发送者,发送过来的消息,交换机类型topic AE交换机: 当业务交换机无法根据指定的routingkey去路由到队列的时候,会全部 ...

  3. 2.RabbitMQ 的可靠性消息的发送

      本篇包含 1. RabbitMQ 的可靠性消息的发送 2. RabbitMQ 集群的原理与高可用架构的搭建 3. RabbitMQ 的实践经验   上篇包含 1.MQ 的本质,MQ 的作用 2.R ...

  4. rabbitmq如何保证消息可靠性不丢失

    目录 生产者丢失消息 代码模拟 事务 confirm模式确实 数据退回监听 MQ事务相关软文推荐 MQ丢失信息 消费者丢失信息 之前我们简单介绍了rabbitmq的功能.他的作用就是方便我们的消息解耦 ...

  5. RabbitMQ消息可靠性传输

    消息的可靠性投递是使用消息中间件不可避免的问题,不管是使用kafka.rocketMQ或者rabbitMQ,那么在RabbitMQ中如何保证消息的可靠性投递呢? 先再看一下RabbitMQ消息传递的流 ...

  6. RabbitMQ的消息可靠性(五)

    一.可靠性问题分析 消息的可靠性投递是使用消息中间件不可避免的问题,不管是使用哪种MQ都存在这种问题,接下来要说的就是在RabbitMQ中如何解决可靠性问题:在前面 在前面说过消息的传递过程中有三个对 ...

  7. [转载]RabbitMQ消息可靠性分析

    有很多人问过我这么一类问题:RabbitMQ如何确保消息可靠?很多时候,笔者的回答都是:说来话长的事情何来长话短说.的确,要确保消息可靠不只是单单几句就能够叙述明白的,包括Kafka也是如此.可靠并不 ...

  8. RabbitMQ消息可靠性分析

    消息中间件的可靠性是指对消息不丢失的保障程度:而消息中间件的可用性是指无故障运行的时间百分比,通常用几个 9 来衡量.不存在绝对的可靠性只能尽量趋向完美.并且通常可靠性也意味着影响性能和付出更大的成本 ...

  9. RabbitMQ消息可靠性分析 - 简书

    原文:RabbitMQ消息可靠性分析 - 简书 有很多人问过我这么一类问题:RabbitMQ如何确保消息可靠?很多时候,笔者的回答都是:说来话长的事情何来长话短说.的确,要确保消息可靠不只是单单几句就 ...

随机推荐

  1. 后端渲染神器!Dust

    Dust一个适用于浏览器与node的异步模板框架. 先上实例 后端模板: {@inject api="http://api.myserver.com/get_message"} & ...

  2. AS之AlertDialog使用

    关于AlertDialog的使用,主要是去做一个弹窗. import android.content.DialogInterface; import android.os.Bundle; import ...

  3. java中异常(Exception)的定义,意义和用法。举例

    1.异常(Exception)的定义,意义和用法 我们先给出一个例子,看看异常有什么用? 例:1.1- public class Test {    public static void main(S ...

  4. CCF201409-3 字符串匹配

    问题描述 给出一个字符串和多行文字,在这些文字中找到字符串出现的那些行.你的程序还需支持大小写敏感选项:当选项打开时,表示同一个字母的大写和小写看作不同的字符:当选项关闭时,表示同一个字母的大写和小写 ...

  5. Python疫情爬取输出到txt文件

    在网上搬了一个代码,现在不适用了,改了改 import requestsimport jsondef Down_data(): url = 'https://view.inews.qq.com/g2/ ...

  6. 带UI的小初高数学学习软件—艰难地用C++(QT库)实现的过程

    从互相了解对方的代码思路然后确定用C++编写,到用win32写界面时变得摇摆不定的考虑着要不要改变语言,再到用QT写完界面后发现短信接口一般都不提供C++,最后到QT打包出来的可执行文件在别的设备上无 ...

  7. EFCore 6.0入门看这篇就够了

    前言 作为一直在dotNet行业耕耘的码农,这几年在大大小小项目中也涉及到了许多ORM框架,比如:EFCore,Dapper,NHibernate,SqlSugar等等,这些ORM都有各自的优缺点,大 ...

  8. Java报错:Injection of resource dependencies failed

    在学习springMVC+Mabatis的时候,添加注解@Resource报错 Injection of resource dependencies failed de完bug后发现有几个点注意一下, ...

  9. Linux系统安装后IP能通端口不通的问题处理方法

    网上大部分都是针对防火墙的问题,这里首先排除防火防火墙导致端口不通的问题! 1.排除防火墙问题(防火墙的排查方式网上一搜全是,这里不再赘述) 2.查看检查端口有没有监听,发现端口未监听(比如8080端 ...

  10. 两数之和_LeetCode_1

    LeetCode_1原题链接:https://leetcode-cn.com/problems/two-sum/ 剑指 Offer 57原题链接: https://leetcode-cn.com/pr ...