RabbitMQ 保证消息可靠性传输

我们知道,RabbltMQ 不能够保证消息不会丢失,那么在高并发场景下,如何保证消息的可靠性传输?

消息可靠性问题

Publisher 发送消息到 Exchange 阶段:

  • Publisher 发送的消息未到达 Exchange。
  • 消息到达 Exchange 未到达 Queue。

Exchange 发送消息到 Queue 阶段:MQ 宕机,Queue 将消息丢失。

Queue 发送消息到 Consumer 阶段:Consmer 接收消息后未消费就宕机。

解决消息可靠性问题

RabbitMQ 提供了解决消息可靠性问题的方案。

生产者到 Queue 阶段消息丢失

生产者消息确认:
RabbitMQ 提供了 publisher confirm 机制来避免消息发送到 MQ 过程中丢失。消息发送到 MQ 后,会返回一个结果,表示消息是否成功。

publisher-confirm,发送者确认。

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

publisher-return,发送者回执。

  • 消息投递到交换机了,但是没有路由到队列。返回 ACK,以及路由失败原因。

注意:确认机制发送消息时,需要给每个消息设置一个全局唯一 id,以区分不同消息,避免 ack 冲突。

ReturnCallback 和 ConfimCallback 进行生产者确认。ReturnCallback 只能够重写一个,而 ConfimCallback 可以每个生产者都进行重写。

# 通过配置开启 生产者消息确认。
spring:
  rabbitmq:
    listener:
      simple:
		publisher-confirm-type: correlated # 异步回调,定义 ConfirmCallback,MQ 返回结果时会回调这个 ConfirmCallback。
	    publisher-returns: true # 开启 publisher-confirm 功能,同样基于 callback 机制,不过是定义 ReturnCallback。
	    template:
	      mandatory: true # 定义消息路由失败的策略。true 调用 ReturnCallback;false 丢弃消息。

重写 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) -> {
            // 判断是否是延迟消息
            Integer receivedDelay = message.getMessageProperties().getReceivedDelay();
            if (receivedDelay != null && receivedDelay > 0) {
                // 是一个延迟消息,忽略这个错误提示
                return;
            }
            // 记录日志
            log.error("消息发送到队列失败,响应码:{}, 失败原因:{}, 交换机: {}, 路由key:{}, 消息: {}",
                     replyCode, replyText, exchange, routingKey, message.toString());
            // 如果有需要的话,重发消息
            // rabbitTemplate.convertAndSend(exchange, routingKey, message);
        });
    }
}

重写 ConfimCallback 方法:

	@Test
    public void testSendMessage2SimpleQueue() throws InterruptedException {
        // 消息
        String msg = "hello, spring amqp";
        // correlationData
        // 1. 准备 id
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        // 2. 准备 ConfirmCallback
        correlationData.getFuture().addCallback( result -> { // 成功
            // 判断结果
            if (result.isAck()) {
                // ACK
                log.debug("消息 {} 成功投递到交换机。", correlationData.getId());
            }else {
                // NACK
                log.error("消息投递到交换机失败!消息 ID:{}", correlationData.getId());
            }
        }, ex -> { // 失败
            // 记录日志
            log.error("消息发送失败,失败原因是:" + ex);
            // 也可以重发消息
        });
        // 发送消息
        rabbitTemplate.convertAndSend("amq.topic", "simple.test", msg, correlationData);
    }

Exchange 到 Queue 阶段

消息传递到 Queue 中,而此时 Queue 宕机,消息丢失。
针对这种情况,可以采取消息持久化的方式:

  1. 交换机持久化(默认持久化)。
  2. 队列持久化(默认持久化)。
  3. 消息持久化,SpringAMQP中的消息默认持久化。
	// 声明持久化交换器 
	@Bean
    public DirectExchange simpleDirect(){
        return new DirectExchange("simple.direct", true, false);
    }
    // 声明持久化队列并绑定
    @Bean
    public Queue simpleQueue(){
        return QueueBuilder.durable("simple.queue").build();
    }
    // 消息持久化
    // 1. 准备消息
    Message message = MessageBuilder.withBody("hello, spring".getBytes(StandardCharsets.UTF_8))
            .setDeliveryMode(MessageDeliveryMode.PERSISTENT) // 持久化消息设置
            .build();

消息进入消费者

消费者消息确认:
消费者处理消息后,向 MQ 发送 ack 回执, MQ 收到 ack 回执后才会删除该消息。

SpringAMQP 允许配置三种模式:
manual:手动 ack,需要在业务代码结束后,调用 api 发送 ack。
auto:自动 ack,由 spring 监测 listener 代码是否出现异常,没有异常则返回 ack;抛出异常则返回nack(推荐)。
none:关闭 ack,MQ 假定消费者获取消息后会成功处理,因此消息投递后立即删除。

消费失败重试机制:
消费者消息确认机制中,消费者消费失败就永无停止的重发,浪费资源。
可以利用 Spring 的 retry 机制,在消费者出现异常时利用本地重试,而不是无限制的 requeue 到队列。

消费者失败消息处理策略
开启重试模式后,重试次数耗尽,如果消息依然失败,则需要 MessageRecoverer 接口来处理,有三种实现:
RejectAndDontRequeueRecoverer:重试耗尽后,直接 reject,丢弃消息(默认)。
ImmediateRequeueMessageRecoverer:重试耗尽后,返回 nack,消息重新入队。
RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定交换机(异常交换机)。

通过 yaml 文件配置消费者确认机制和 Spring 重试机制。

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: auto # 消费者确认机制 自动 ack,由 spring 监测 listener 代码是否出现异常,没有异常则返回 ack;抛出异常则返回nack(推荐)。
        retry: # 启用 spring 的 retry 机制
          enabled: true # 开启消费者失败重试
          initial-interval: 1000 # 初始的失败等待时长为 1 s
          multiplier: 3 # 下次失败的等等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 4 # 最大尝试次数
          stateless: true # true 无状态;false 有状态。如果业务包含事务,改为 false。

配置异常队列与失败消息处理策略:

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


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


    @Bean
    public Binding errorMessageBinding(){
        return BindingBuilder.bind(errorQueue()).to(errorMessageExchange()).with("error");
    }

    /**
     * 消费者失败消息处理策略
     * @param rabbitTemplate
     * @return
     */
    @Bean
    public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
    }
### RabbitMQ 确保消息可靠性传输的最佳实践 #### 生产者确认机制 为了确保消息能成功发送至 RabbitMQ 服务器,生产者应启用确认模式。当消息被写入内存并准备投递给至少一个消费者时,RabbitMQ 将向发布者返回一条确认信息。这种方式可以有效预防因网络波动或其他异常情况造成的消息丢失[^2]。 ```python import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 开启 publisher confirms 模式 channel.confirm_delivery() ``` #### 消息持久化设置 即使消息已抵达队列内部,若仅保存于内存之中,在遭遇服务崩溃的情况下仍会面临数据遗失的风险。因此建议开启消息持久化选项,使得每条记录都被同步到磁盘文件里,以此保障即便发生意外停机也能恢复未处理完毕的信息流[^3]。 ```python properties=pika.BasicProperties(delivery_mode=2,) # 设置 delivery_mode 为 2 表明该消息需要持久化 channel.basic_publish(exchange='', routing_key='task_queue', body=message, properties=properties) ``` #### 队列声明参数调整 创建队列时指定其具备持久属性同样重要,这样可使整个队列结构得以长期保留下来而不受临时性的节点重启影响。同时还可以考虑设定`x-max-priority`来支持优先级队列功能,以及利用死信交换器(`Dead Letter Exchanges`)增强系统的健壮性和灵活性。 ```python arguments={ 'x-message-ttl': 60000, # TTL (Time To Live), 单位毫秒 'x-dead-letter-exchange': 'dlx', # 死信转发给 dlx exchange } channel.queue_declare(queue='durable_queue', durable=True, arguments=arguments) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值