微服务(SpringCloud)第五篇之RabbitMQ(消息队列高级篇)

作者简介:☕️大家好,我是intelligent_M,一个Java后端开发者!
当前专栏:intelligent_M—— 微服务(SpringCloud) ,CSDN博客。

后续会更新Java相关技术栈以及链表哈希表二叉树…,回溯算法贪心算法…等等算法题。
创作不易 欢迎点赞评论!!!


RabbitMQ(高级篇)

消息的可靠性问题

在这里插入图片描述

发送者的可靠性

生产者的重连
  • 有时候由于网络波动,可能会出现客户端连接MQ失败的情况。通过配置我们可以开启连接失败后的重连机制:
    在这里插入图片描述
  • 注意:当网络不稳定的时候,利用重试机制可以有效提高消息发送的成功率。不过SpringAMQP提供的重试机制是阻塞式的重试,也就是说多次重试等待的过程中,当前线程是被阻塞的。
    如果对于业务性能有要求,建议禁用重试机制。如果一定要使用,请合理配置等待时长和重试次数,当然也可以考虑使用异步线程来执行发送消息的代码。
生产者确认
  • RabbitMQ有PublisherConfirmPublisherReturn两种确认机制。开启确认机制后,在MQ成功接收到消息后会返回确认消息给生产者。返回的结果有以下几种情况:

  • 消息投递到了MQ,但是路由失败。此时会通过PublisherReturn返回路由异常原因,然后返回ACK,告知投递成功

  • 临时消息投递到了MQ,并且入队成功,返回ACK,告知投递成功

  • 持久消息投递到了MQ,并且入队完成持久化,返回ACK,告知投递成功

  • 其他情况都会返回NACK,告知投递失败
    在这里插入图片描述

  • SpringAMQP实现生产者确认

  • 1.在publisher这个微服务的application.yaml中添加配置:
    在这里插入图片描述

  • 这里的publisher-confirm-type有三种模式可选:

    • none:关闭confirm机制
    • simple:同步阻塞等待MQ的回执消息
    • correlated:MQ异步回调方式返回回执消息
  • 2.每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目启动过程中配置:
    在这里插入图片描述

  • 3.发送消息,指定消息ID,消息ConfirmCallback
    在这里插入图片描述

  • SpringAMQP中生产者消息确认的几种返回值情况:

    • 消息投递到了MQ,但是路由失败。会return路由异常原因,返回ACK
    • 临时消息投递到了MQ,并且入队成功,返回ACK
    • 持久消息投递到了MQ,并且入队完成持久化,返回ACK
    • 其他情况都会返回NACK,告知投递失败
  • 如何处理生产者的确认消息?

    • 生产者确认需要额外的网络和系统资源开销,尽量不要使用
    • 如果一定要使用,无需开启Publisher-Return机制,因为一般路由失败是自己业务问题
    • 对于nack消息可以有限次数重试,依然失败则记录异常信息

MQ的可靠性

  • 在默认情况下,RabbitMQ会将接收到的消息保存在内存中以降低消息收发的延迟。这样会导致两个问题:
  • 一旦MQ宕机,内存中的消息会全丢失
  • 内存空间有限,消费者处理故障或处理过慢时,会导致消息积压,引发MQ阻塞
    在这里插入图片描述
数据持久化
  • RabbitMQ实现数据持久化包括3个方面
  • 交换机持久化
    在这里插入图片描述
  • 队列持久化
    在这里插入图片描述
  • 消息持久化
    在这里插入图片描述
Lazy Queue
  • 从RabbitMQ3.6.0版本开始,就增加了LazyQueue的概念,也就是惰性队列。

  • 惰性队列的特征如下:

    • 接收到消息后直接存入磁盘而非内存(内存中只保留最近的消息,默认2048条)
    • 消费者要消费消息时才会从磁盘中读取并加载到内存
    • 支持数百万条的消息存储
      在3.12版本后,所有队列都是LazyQueue模式,无法更改
  • 要设置一个队列为惰性队列,只需在声明队列时,指定x-queue-mode属性为lazy即可:

  • 控制台声明
    在这里插入图片描述

  • Java代码中声明(基于Bean或者注解)
    在这里插入图片描述

  • RabbitMQ如何保证消息的可靠性

  • 首先通过配置可以让交换机,队列,以及发送的消息都持久化。这样队列中的消息都会持久化到磁盘,MQ重启消息依然存在

  • RabbitMQ在3.6版本引入了LazyQueue,并且在3.12版本后会成为队列的默认模式。LazyQueue会将所有消息都持久化。

  • 开启持久化和生产者确认时,RabbitMQ只有在消息持久化完成后才会给生产者返回ACK回执

消费者的可靠性

消费者确认机制
  • 为了确认消费者是否成功处理消息,RabbitMQ提供了消费者确认机制(Consumer Acknowledfement)。当消费者处理消息结束后,应该向RabbitMQ发送一个回执,告知RabbitMQ自己消息处理状态。回执有三种可选值:
  • ack:成功处理消息,RabbitMQ从队列中删除该消息
  • nack:消息处理失败,RabbitMQ需要再次投递消息
  • reject:消息处理失败并拒绝该消息,RabbitMQ从队列中删除该消息
    在这里插入图片描述
  • SpringAMQP已经实现了消息确认功能。并允许我们通过配置文件选择ACK处理方式,有三种方式:
    • none:不处理。及消息投递给消费者后立刻ack,消息会立刻从MQ删除。非常不安全,不建议使用
    • manual:手动模式。需要自己在业务代码中调用api,发送ack或reject,存在业务入侵,但更灵活
    • auto:自动模式。SpringAMQP利用AOP对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回ack
  • 当业务出现异常时,根据异常判断返回不同结果:
    • 如果时业务异常,会自动返回nack
    • 如果是消息处理或校验异常,自动返回reject
      在这里插入图片描述
消费失败处理
  • 失败重试机制

  • 当消费者出现异常后,消息会不断requeue(重新入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力

  • 我们可以利用Spring的retry机制,再消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列:
    在这里插入图片描述

  • 失败消息处理策略

  • 在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有MessageRecoverer接口来处理,它包含三种不同的实现:

  • RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢失消息。默认就是这种方式

  • ImmediateRequeueMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机**(推荐使用)**
    在这里插入图片描述

  • 将失败处理策略改为RepublishMessageRecoverer:

  • 1.首先,定义接收失败消息的交换机,队列及其绑定关系

  • 2.然后,定义RepublishMessageRecoverer:
    在这里插入图片描述

@Configuration
//该注解是当某个属性,满足条件时生效(当开启超时重试机制时生效)
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry", name = "enabled", havingValue = "true")
public class ErrorConfiguration {

    @Bean
    public DirectExchange errorExchange(){
        return new DirectExchange("error.direct");
    }

    @Bean
    public Queue errorQueue(){
        return new Queue("error.queue");
    }

    @Bean
    public Binding errorBinding(Queue errorQueue , DirectExchange errorExchange){
        return BindingBuilder.bind(errorQueue).to(errorExchange).with("error");
    }

    @Bean
    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate, "error.direct","error");
    }
}
  • 效果如下(会把错误信息发送到error队列交由人工处理)
    在这里插入图片描述
  • 消费者如何保证消息一定被消费?
    • 开启消费者确认机制为auto,由spring确认消息处理成功后返回ack,异常时返回nack
    • 开启消费者失败重试机制,并设置MessageRecoverer,多次重试失败后将消息投递到异常交换机,交由人工处理
业务幂等性
  • 幂等是一个数学概念,用函数表达式来描述是这样的:f(x)= f(f(x))。再程序开发中,则是指同一个业务,执行一次或者多次对业务状态的影响是一致的。
    在这里插入图片描述
  • 对于非幂等的业务我们需要将其变为幂等业务
  • 唯一消息id
  • 方案一,是给每个消息都设置一个唯一id,利用id区分是否是重复消息:
    • 1.每一条消息都生成一个唯一的id,与消息一起投递给消费者
    • 2.消费者接收到消息后处理自己的业务,业务处理成功后将消息保存到数据库
    • 3.如果下次又收到相同的消息,去数据库中查询判断是否存在,存在则为重复消息放弃处理
      在这里插入图片描述
  • 方案二,是结合业务逻辑,基于业务本身做判断。以下面业务为例:我们要在支付后修改订单状态为已支付,应该在修改订单状态前先查询订单状态,判断状态是否未支付。只有未支付订单才需要修改,其他状态不做处理:
    在这里插入图片描述
  • 如何保证支付服务与交易服务之间的订单状态一致性?
    • 首先,支付服务会正在用户支付成功以后利用MQ消息通知交易服务,完成订单状态同步。
    • 其次,为了保证MQ消息的可靠性,我们采用了生产者确认机制、消费者确认、消费者失败重试等策略,确保消息投递和处理的可靠性。同时也开启了MQ的持久化,避免因服务宕机导致消息丢失。
    • 最后,我们还在交易服务更新订单状态时做了业务幂等判断,避免因消息重复消费导致订单状态异常。
  • 如果交易服务消息处理失败,有没有什么兜底方案?
    • 我们可以在交易服务设置定时任务,定期查询订单支付状态。这样即便MQ通知失败,还可以利用定时任务作为兜底方案,确保订单支付状态的最终一致性。

延迟消息

  • 延迟消息:生产者发送消息时指定一个时间,消费者不会立刻收到消息,而是在指定时间之后才收到消息。
  • 延迟任务:设置在一定时间之后才执行的任务
    在这里插入图片描述
死信交换机
  • 当一个队列中的消息满足下列情况之一时,就会成为死信(dead letter):

    • 消费者使用basic.reject或basic.nack声明消费失败,并且消息的 requeue参数设置为false
    • 消息是一个过期消息(达到了队列或消息本身设置的过期时间),超时无人消费
    • 要投递的队列消息堆积满了,最早的消息可能成为死信
  • 如果队列通过dead-letter-exchange属性指定了一个交换机,那么该队列中的死信就会投递到这个交换机中。这个交换机被称为死信交换机(Dead Letter Exchange,简称DLX)
    在这里插入图片描述
    在这里插入图片描述

  • 死信交换机的方法比较麻烦不推荐使用

延迟消息插件
  • RabbitMQ的官方也推出了一个插件,原生支持延迟消息功能。该插件的原理是设计了一种支持消息功能的交换机,当消息投递到交换机后可以暂存一定的时间,到期后再投递到队列。
  • 基于注解或者Bean进行配置
    在这里插入图片描述
  • 发送消息时需要通过消息x-delay来设置过期时间:
    在这里插入图片描述
  • 注意:
    延迟消息插件内部会维护一个本地数据库表,同时使用Elang Timers功能实现计时。如果消息的延迟时间设置较长,可能会导致堆积的延迟消息非常多,会带来较大的CPU开销,同时延迟消息的时间会存在误差。
    因此,不建议设置延迟时间过长的延迟消息
取消超时订单
  • 设置30分钟后检测订单支付状态实现起来非常简单,但是存在两个问题
    • 如果并发比较高,30分钟可能堆积消息过多,对MQ压力很大
    • 大多数订单再下单后1分钟内就会支付,但是却需要在MQ内等待30分钟,浪费资源
  • 所以我们把30分钟拆解成多个时间间隔,去查询订单状态修改支付订单在这里插入图片描述
  • 例如:我们在用户下单后的第10秒、20秒、30秒、45秒、60秒、1分30秒、2分、…30分分别设置延迟消息,如果提前发现订单已经支付,则后续的检测取消即可。
    这样就可以有效避免对MQ资源的浪费了。

优化后的实现思路如下:
在这里插入图片描述


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Intelligent_M

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值