(六)消息队列——如何确保 RabbitMQ 的消息可靠性

消息可靠性投递:

一、问题分析:

消息从生产者发送到exchange,再到queue,再到消费者,有哪些导致消息丢失的可能性?

1.1 发送时丢失:

a. 生产者发送的消息未送达exchange
b. 消息到达exchange后未到达queue

1.2 MQ宕机;
1.3 consumer接收到消息后未消费就宕机

二、问题解决:

1、生产者消息确认

1.1 理论:

RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。结果有两种请求:

  1. publisher-confirm,发送者确认:消息成功投递到交换机,返回ack;消息未投递到交换机,返回nack
  2. publisher-return,发送者回执:消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因。

在这里插入图片描述注:消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因。

1.2 实现:
  1. 在publisher这个微服务的application.yml中添加配置:
spring:
  rabbitmq:
    publisher-confirm-type: correlated
    publisher-returns: true
    template:
      mandatory: true

消息确认机制有两种:
simple:生产者同步等待confirm结果,直到超时(不推荐)
correlated:生产者异步回调,定义ConfirmCallback,MQ返回结果时会回调这个ConfirmCallback(推荐),需要给消息设置UUID;

publish-returns:
开启publish-return功能,同样是基于callback机制,不过是定义ReturnCallback(它是全局的,需要在项目启动的时候加载)

template.mandatory:
true,则调用ReturnCallback;
false:则直接丢弃消息

  1. 每个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());
           // 如果需要,可以重新发送消息。
            });
        }
    }
  1. 发送消息,指定消息ID、消息ConfirmCallback
    消息成功发送到exchange,返回ack
    消息发送失败,没有到达交换机,返回nack
    消息发送过程中出现异常,没有收到回执
	@Test
    public void testSendMessage2SimpleQueue() throws InterruptedException {
        // 消息体
        String message = "hello, spring amqp!";
        // 消息ID,需要封装到CorrelationData中
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        // 添加callback
        correlationData.getFuture().addCallback(
                result -> {
                    if (result.isAck()) {
                        // ack,消息成功
                        log.debug("消息发送成功, ID:{}", correlationData.getId());
                    } else {
                        // nack,消息失败
                        log.error("消息发送失败, ID:{}, 原因{}", correlationData.getId(), result.getReason());
                    }
                },
                ex -> log.error("消息发送异常, ID:{}, 原因{}", correlationData.getId(), ex.getMessage()));
        // 发送消息。由于需要做消息确认机制,所以要添加参数correlationData
        rabbitTemplate.convertAndSend("amq.direct", "simple", message, correlationData);
    }

2、消息持久化

2.1 理论:

即使保证了消息可靠地发到了MQ中,但MQ的消息存储在内存中,一旦宕机,也一样还是会丢失消息。 所以为了解决这个问题,我们应该把消息持久化到磁盘中。

2.2 实现:

MQ默认是内存存储消息,开启持久化功能可以确保缓存在MQ中的消息不丢失。

  1. 交换机持久化:
	@Bean
    public DirectExchange simpleExchange(){
        // 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除       
        return new DirectExchange("simple.direct", true, false);  }
    }
  1. 队列持久化:
	@Bean  
    public Queue simpleQueue(){     
        // 使用QueueBuilder构建队列,durable就是持久化的      
        return QueueBuilder.durable("simple.queue").build();  
    }
  1. 消息持久化,SpringAMQP中的消息默认是持久的,可以通过MessageProperties中的DeliveryMode来指定的:
Message msg = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8)) // 消息体
             .setDeliveryMode(MessageDeliveryMode.PERSISTENT) // 持久化
             .build();

3、消费者消息确认

3.1 理论:

RabbitMQ支持消费者确认机制,即:消费者处理消息后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息。而SpringAMQP则允许配置三种确认模式:

  1. manual:手动ack,需要在业务代码结束后,调用api发送ack。
  2. auto:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack
  3. none:关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除
    在这里插入图片描述

4、消费失败重试机制

4.1 理论:

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

我们可以利用Spring的retry机制,在消费者出现异常时利用本地重试,而不是交给MQ进行无限制的requeue到mq队列。当重试打到最大次数后仍然不成功,则将这条消息丢弃掉。

在这里插入图片描述关于消息被丢弃,新的策略如下:
消息失败重试策略,是根据MessageRecoverer接口来处理的,它包含了三种不同的实现:
a. RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
b. ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
c. RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机
在这里插入图片描述

4.2 实现:
  1. 首先,定义接收失败消息的交换机、队列及其绑定关系:
	@Bean
    public DirectExchange errorMessageExchange(){
        return new DirectExchange("error.direct");
    }
    @Bean
    public Queue errorQueue(){
        return new Queue("error.queue", true);
    }
    @Bean
    public Binding errorBinding(){
        return BindingBuilder.bind(errorQueue()).to(errorMessageExchange()).with("error");
    }
  1. 然后,定义RepublishMessageRecoverer的Bean覆盖SpringBoot的默认配置:
	@Bean 
    public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){ 
        return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error"); 
    }

三、总结:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值