RabbitMQ的几个关键问题

消息可靠性

- RabbitMQ如何保证消息可靠性?

- RabbitMQ如何保证消息不丢失?

RabbitMQ通过以下方式来保证消息的可靠性和防止消息丢失:

  1. 持久化:RabbitMQ允许将消息标记为持久化,这意味着消息将会被写入磁盘而不是仅保存在内存中。这样,在消息发送到队列之后,即使RabbitMQ服务器发生故障或重启,消息也能够存储在磁盘上,并在恢复后仍然可用。
  2. 发布确认机制(Publisher Confirms):生产者可以使用发布确认机制来确保消息已经成功地发送到RabbitMQ。通过对每条消息进行确认,生产者可以得知消息是否已经安全地传递到RabbitMQ服务器。如果未收到确认或收到失败的确认,生产者可以选择重发消息,以确保消息的可靠传递。

而对于防止消息丢失的机制,RabbitMQ提供了以下方法:

  1. 消费者确认机制(Consumer Acknowledgements):消费者在正确处理消息后,可以向RabbitMQ发送确认。这样,在消息被消费并确认之前,RabbitMQ不会将其从队列中删除。如果消费者在处理消息期间发生错误,消息将会被重新投递给其他消费者,从而避免了消息的丢失。
  2. 消息预取(Message Prefetch):RabbitMQ允许消费者一次从队列中获取多个消息,并将它们存储在本地缓冲区中。这样可以提高消费者的效率,并减少消费者与RabbitMQ服务器之间的通信次数。如果消费者崩溃或断开连接,尚未确认的消息将会被重新投递给其他消费者,从而防止消息的丢失。

使用这些机制,可以最大程度地确保消息的可靠性和防止消息丢失,但仍需根据实际需求和场景进行配置和调优。

- RabbitMQ发送与消费消息的模型

diff 复制代码
-

消息丢失的几种情况?

  • 1、生产者发送消息未到达交换机
  • 2、消息到达交换机,没有正确路由到队列
  • 3、MQ宕机,队列中的消息不见了
  • 4、消费者收到消息,还没消费完,消费者宕机

如何保证消息不丢失?

  1. 消息持久化:在消息发布时,将消息标记为持久化。这样,即使在RabbitMQ服务器崩溃或重启时,消息也会被写入磁盘而不会丢失。
  2. 发布确认机制:生产者可以使用发布确认机制来确认消息是否成功发送到RabbitMQ。在发送消息后,等待来自RabbitMQ的确认回复。如果未收到确认或收到了发送失败的确认,可以选择重发消息以确保消息的可靠传递。
  3. 消费者确认机制:消费者在正确处理消息后,向RabbitMQ发送确认。这样,在消息被消费并确认之前,RabbitMQ不会将其从队列中删除。如果消费者在处理期间发生错误,消息会被重新投递给其他消费者。
  4. 发布与确认的事务模式:将发布和确认操作包装在事务中。这样,只有在确认事务提交后,消息才会被RabbitMQ真正接收和保存。如果事务回滚或失败,消息发送将被撤销,避免消息的丢失。
  5. 适当的消息确认模式:根据应用程序的要求,选择适当的消息确认模式。例如,在处理消息之前先接收到后续消息,然后再确认前面的消息,以确保消息的顺序性和完整性。
  6. 高可用性和冗余设置:配置多个RabbitMQ节点,使用镜像队列或集群来提高可用性和冗余性。这样,即使一个节点发生故障,消息仍可被其他节点接收和处理。
  7. 监控和报警:设置监控和报警机制来及时检测和处理消息丢失或其他异常情况,以减少潜在的风险。

如何解决呢

- 消息能不能丢?
markdown 复制代码
-   得看

    -   不太重要的消息,丢了也没事
    -   重要的消息,跟事务数据一致性相关的消息,不能丢的
- 1、生产者确认机制
markdown 复制代码
    -   1、publisher-confirm

        -   消息成功投递到交换机,返回ack

        -   消息未成功投递到交换机,返回nack

            -   如何失败,就要重新发送

    -   2、publisher-return

        -   未正确到达队列,返回ack及失败原因

    -   3、图示

        -

- 4、实现

ruby 复制代码
        -   1、配置文件

            -   spring: rabbitmq: publisher-confirm-type: correlated publisher-returns: true template: mandatory: true

                -   publish-confirm-type:开启publisher-confirm

                    -   simple:同步等待confirm结果,直到超时
                    -   correlated:异步回调,定义ConfirmCallback,MQ返回结果时会回调这个ConfirmCallback

                -   publish-returns:开启publish-return,同样是基于callback机制,不过是定义ReturnCallback

                -   template.mandatory:定义消息路由失败时的策略。

                    -   true,则调用ReturnCallback
                    -   false:则直接丢弃消息

        -   2、定义ConfirmCallback

            -   ConfirmCallback可以在发送消息时指定,因为每个业务处理confirm成功或失败的逻辑不一定相同。

                -   public void testSendMessage2SimpleQueue() throws InterruptedException { // 1.消息体 String message = "hello, spring amqp!"; // 2.全局唯一的消息ID,需要封装到CorrelationData中 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 3.添加callback correlationData.getFuture().addCallback( result -> { if(result.isAck()){ // 3.1.ack,消息成功 log.debug("消息发送成功, ID:{}", correlationData.getId()); }else{ // 3.2.nack,消息失败 log.error("消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason()); } }, ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage()) ); // 4.发送消息 rabbitTemplate.convertAndSend("", "simple.queue", message, correlationData); // 休眠一会儿,等待ack回执 Thread.sleep(2000); }
                -

        -   3、定义Return回调

            -   每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目加载时配置。

                -   @Slf4j @Configuration public class CommonConfig implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 获取RabbitTemplate RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class); // 设置ReturnCallback rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { // 投递失败,记录日志 log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}", replyCode, replyText, exchange, routingKey, message.toString()); // 如果有业务需要,可以重发消息 }); } }
                -
- 持久化机制
markdown 复制代码
    -   1、交换机持久化

        -   默认就是持久化

    -   2、队列持久化

        -   默认就是持久化

    -   3、消息持久化

        -   默认就是持久化

        -   在发送消息时,使用Message对象,并设置delivery-mode为持久化

            -
- 消费者ack机制
markdown 复制代码
    -

    -   ack取值

        -   none:只要消息到达消费者,Spring直接返回ack到MQ

            -   MQ收到ack,会把队列中的消息删除

            -   消息会丢失

            -   演示

                -   消费者配置

                    -   spring: rabbitmq: listener: simple: acknowledge-mode: none # 关闭ack

        -   manual:手动ack

            -   消费成功,调用API给MQ返回ack

            -   消费失败,调用API给MQ返回nack,并且让消息重回队列

            -   演示

                -   消费者配置

                    -   spring: rabbitmq: listener: simple: acknowledge-mode: manual #手动ack

                -   消费者代码

                    -

        -   auto:自动ack【默认值】。消费消息不出异常,返回ack给MQ。消费消息出异常了,返回nack,把消息重回队列

            -   1、本地重试

                -   消费者在消费消息时,如果失败了,则在本地重试,重新消费,如果达到重试次数,还是失败,则返回ack,不重回队列,MQ会删除队列中的消息
                -   spring: rabbitmq: listener: simple: retry: enabled: true #开启消费者失败重试 initial-interval: 1000 #初始的失败等待时长为1秒 multiplier: 2 #失败的等待时长倍数,下次等待时长 = multiplier * last-interval max-attempts: 3 #最大重试次数 stateless: true #true无状态;false有状态。如果业务中包含事务,这里改为false

            -   2、失败策略

                -   1、RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式

                -   2、ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队

                -   3、RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机

                    -

            -   3、使用 RepublishMessageRecoverer

                -   需求:把消息投递到失败的交换机,路由队列。记录日志,将来人工干预

                -   实现

                    -   1、定义错误交换机、队列、绑定关系。定义RepublishMessageRecoverer

                        -

                    -   3、监听错误队列

                        -

--

diff 复制代码
-   1、生产者开启confirm机制,保证消息正确到达交换机
-   2、生产者开启return机制,保证消息正确到达队列
-   3、交换机、队列、消息进行持久化
-   4、消费者开启手动ack,或者自动ack + 重试耗尽的失败策略,定义错误交换机队列,后期通过人工进行干预

- 消息重复消费问题

要解决消息重复消费的问题,可以考虑以下几种方法:

  1. 唯一标识符和去重:在生产者端或消息内容中添加唯一标识符,用于标识每条消息的唯一性。消费者在处理消息前,先检查该标识符是否已经处理过相同的消息,如有则进行去重操作。

  2. 消费者端幂等性:设计消费者端的处理逻辑具有幂等性。即无论消息被处理多次,最终结果都保持一致。这样,即使消息被重复消费,也不会对最终结果产生影响。

  3. 消费者确认模式(手动应答):在消费者处理消息后,通过显式发送确认(acknowledgement)给RabbitMQ,确认消息已经被成功处理。RabbitMQ在收到确认后,才会将消息从队列中删除。如果消费者在处理消息过程中发生错误,可以选择不发送确认,使消息重新投递给其他消费者。

  4. 幂等性队列:某些消息队列系统或中间件提供了幂等性队列的支持。这样的队列会自动处理重复消息,并确保同一条消息不会被重复消费。

  5. 消息超时设置:在消息中设置一个合理的超时时间。如果消费者未能及时处理消息,超过超时时间后消息会被重新投递给其他消费者,避免了消息长时间占据队列而无法被处理的问题。

  6. 监控和日志记录:建立完善的监控系统,及时检测和记录消息消费的状态和异常情况。通过监控和日志记录,可以对消息的消费情况进行追踪和分析,及时发现重复消费问题并进行处理。

    • 1、问题

      • 同一个消息如何保证重复消费问题?
      • 消息如何保证幂等性?
    • 2、答:消费者的操作是否是一个幂等性操作?

      • 什么是幂等性?

        • 多次执行同一个操作,最终的结果是一样的

          • 幂等

            • 查询

            • 根据条件删除

              • 第一次,删除成功
              • 第二次及以后,虽然数据已经不存在了,但是删除操作也是成功的,只是没有删除数据。所以也不影响
            • 更新

              • update 表名 set 字段=值 where id = ?
          • 非幂等性

            • 增加

              • 多次执行,会插入多少数据
            • 更新

              • update 表名 set 字段=字段 - 值 where id = ?
            • 保存订单、扣减库存等操作

      • 对于幂等性操作,多次消费消息,除开性能的影响 ,其他没有什么大问题,可以不管它

      • 对于非幂等性操作,多次消费消息,会造成数据一致性的问题,所以要保证重复消费消息的问题

-

- 消息积压问题

要解决消息积压问题,可以考虑以下几个方面:

  1. 消费者数量和消费速度:增加消费者的数量,以提高消息的处理速度。通过水平扩展消费者,可以分摊每个消费者的负载,从而加快消息的处理速度。
  2. 提高消费者处理能力:优化消费者的处理逻辑和代码,提高处理消息的效率和速度。可以考虑并发处理消息、批量处理消息等方式来提高消费者的处理能力。
  3. 动态调整消费者数量:根据消息队列中消息的数量和消费者的处理速度,动态调整消费者的数量。通过监控消息队列积压情况,自动增加或减少消费者的数量,以适应消息的负载。
  4. 增加队列容量:增加消息队列的容量,以容纳更多的消息。增加队列容量可以减少消息的排队等待时间,从而降低消息积压的风险。
  5. 定时任务和重试机制:定期检查消息队列中的积压情况,并尝试重新投递或延迟处理积压的消息。通过设置合适的重试机制和延迟时间,可以有效地处理消息积压的情况。
  6. 监控和报警:设置监控系统来实时监测消息队列的积压情况。当积压量超过一定阈值时,及时触发报警机制,通知相关人员进行处理和调整。
  7. 异步处理和削峰填谷:将消息的处理过程异步化,通过引入消息中间件和异步处理机制,将消息的产生和消费解耦。这样可以使消息的处理能力更加灵活,同时也能够满足消息的高峰期和低谷期之间的处理需求。
    • 1、产生的原因?

      • 生产者生产消息的速度 远高于 消费者消费消息的速度?于是就会造成消息积压在MQ中
    • 2、分析为什么会有消息积压?

      • 1、设计是否有问题?

        • 如果是,重新设置生产者与消费者数量匹配

            • 一个生产者
            • 多个消费者
        • 优化架构

      • 2、消费者出问题?

        • 1、消费者出异常了

          • 修改消费者代码,让消费者正常工作
        • 2、消费者宕机了

          • 第一步:修复宕机的情况
          • 第二步:临时开启多个消费者,来以多倍速消费积压的消息。当积压的消息消费的差不多的情况,关闭临时消费者
    • 3、惰性队列

      • 可以放很多消息,还可以把多的消息持久化到本地
相关推荐
齐 飞1 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod1 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。2 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man2 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*3 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu3 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s3 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子3 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
想进大厂的小王3 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea