RabbitMQ的高级特性(消息可靠性)

目录

MQ常见问题

 消息可靠性问题

生产者消息确认

如何确定消息唯一性:

Publisher发送消息的配置

1.全局配置一个配置类,完成对消息由交换机发送到队列的监听

 2.发送消息,指定消息的id,消息的ConfirmCallback

SpringAMQP处理消息确认的几种情况:

MQ把消息弄丢的处理

消费者消息的确认

我们可以利用Spring的retry机制 

消费者消息处理失败策略


这个文章不错

(30条消息) RabbitMQ之消息可靠性问题(含Demo工程)_一切总会归于平淡的博客-CSDN博客_rabbitmq不可靠

MQ常见问题

消息可靠性问题:需要保证消息一定要被消费

延迟消息问题:消息延迟投递的实现,比如多久之后开始被处理,后面还会滋生消息堆积问题

消息堆积问题:百万消息堆积如何被消费

高可用问题:如何避免单点MQ故障而导致不可用问题

 消息可靠性问题

1.发送时丢失:生产者发送消息未到交换机Exchange就丢失,交换机根据RoutingKey路由到Queue途中丢了消息

2.消息到达了队列中,但是MQ宕机,由于MQ是基于内存存储,消息保存内存中的,一宕机就没了

3.消费者接受到消息还没消费就宕机

RabbitMQ的高级特性(消息可靠性)_第1张图片

生产者消息确认

MQ提供了pushlisher confirm机制来避免消息发送到MQ过程中丢失——>当消息发送到MQ以后,会返回一个结果给到publisher表示消息是否处理成功

1.publisher-confirm:发送者确认

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

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

2.publisher-return:发送者回执

消息投递到交换机,但是没有路由到队列。返回ACK并且含有失败原因

如何确定消息唯一性:

确认机制发送消息时,会给每一个消息设定一个全局唯一id,以此区分不同消息

RabbitMQ的高级特性(消息可靠性)_第2张图片

Publisher发送消息的配置

1.publish-confirm-type: 消息发送确认同步/异步

2.publish-returns:开启publish-return功能,发送回执(这里用的是ReturnCallback

3.template.mandatory:定义消息路由失败的策略,true:调用ReturnCallback

RabbitMQ的高级特性(消息可靠性)_第3张图片

 SpringAmQP实现生产者确认,也就是回调

1.全局配置一个配置类,完成对消息由交换机发送到队列的监听

这里失败后记录日志或者重试都可以,ApplicationAware相当于全局应用的一个通知

这里配置的是一个ReturnCallback,也就是路由到队列

RabbitMQ的高级特性(消息可靠性)_第4张图片

我们这里交换机绑定了队列

package cn.itcast.mq.config;

import cn.itcast.mq.admin.MqConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author diao 2022/5/26
 */
@Configuration
public class MqConfig {
    /**
     * 得到一个交换机定义
     */
    @Bean
    public TopicExchange topicExchange() {
        //第二个是保证交换机持久化
        return new TopicExchange(MqConstants.EXCHANGE, true, false);
    }

    /**
     * 定义两个消息队列
     */
    @Bean
    public Queue insertQueue() {
        return new Queue(MqConstants.QUEUE, true);
    }


    /**
     * 将交换机与消息队列进行绑定
     */
    @Bean
    public Binding insertQueueBinding() {
        return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.KEY);
    }

}

 SpringAMQP_Fairy要carry的博客-CSDN博客

package cn.itcast.mq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@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.error("消息发送队列失败,响应码:{},失败原因:{},交换机:{},路由key:{},消息:{}",
                    replyCode,replyText,exchange,routingKey,message.toString());

            //重发消息
            rabbitTemplate.convertAndSend("camq.topic",routingKey,"push again");
        } ));

    }
}

 2.发送消息,指定消息的id,消息的ConfirmCallback

三种情况:1.消息发送到Exchange,返回ack,2.消息发送失败,没有稻交换机,返回nack

3.消息发送出现异常没有收到回执

这里对比于上面封装了消息ID

RabbitMQ的高级特性(消息可靠性)_第5张图片

package cn.itcast.mq.spring;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.CorrelationDataPostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.concurrent.FailureCallback;
import org.springframework.util.concurrent.SuccessCallback;

import java.util.UUID;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMessage2SimpleQueue() throws InterruptedException {
        //1.消息和key
        String routingKey = "insert";
        String message = "hello, spring amqp!";

        //2.准备CorrelationData ,封装消息id唯一性
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());

        correlationData.getFuture().addCallback(result -> {
            //2.1投递消息成功               
            if (result.isAck()) {
                log.debug("消息投递到交换机成功,消息ID:{}", correlationData.getId());
            } else {
            //2.2投递消息失败    
                log.error("消息投递到交换机失败,消息ID:{}", correlationData.getId());
            }
        }, throwable -> log.error("消息发送失败",throwable));


        rabbitTemplate.convertAndSend("camq.topic", routingKey, message,correlationData);
    }
}

 结果:

RabbitMQ的高级特性(消息可靠性)_第6张图片

没有到达指定队列

RabbitMQ的高级特性(消息可靠性)_第7张图片

SpringAMQP处理消息确认的几种情况:


RabbitMQ的高级特性(消息可靠性)_第8张图片


MQ把消息弄丢的处理

1.首先我们模拟一下,在消费者服务中创建一个交换机和一个消息队列,监听队列消息

 @Bean
    public DirectExchange simpleDirect(){
        return new DirectExchange("simple.direct",true,false);
    }

    @Bean
    public Queue simpleQueue(){
        return QueueBuilder.durable("simple.queue").build();
    }

2.然后给simple.queue发个消息,然后将mq关闭重启看是否消息持久化 

发现重启后消息就丢了 

RabbitMQ的高级特性(消息可靠性)_第9张图片

 3.发现消息是不能持久化的,所以我们需要用MessageProperties进行处理

RabbitMQ的高级特性(消息可靠性)_第10张图片


@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMessage2SimpleQueue() throws InterruptedException {
        //1.消息和key
        String routingKey = "insert";
        String message = "hello, spring amqp!";

        //2.准备CorrelationData ,封装消息id唯一性
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());

        correlationData.getFuture().addCallback(result -> {
            //2.1投递消息成功
            if (result.isAck()) {
                log.debug("消息投递到交换机成功,消息ID:{}", correlationData.getId());
            } else {
                //2.2投递消息失败
                log.error("消息投递到交换机失败,消息ID:{}", correlationData.getId());
            }
        }, throwable -> log.error("消息发送失败", throwable));


        rabbitTemplate.convertAndSend("camq.topic", routingKey, message, correlationData);
    }

    /**
     * 持久化发信息
     */
    @Test
    public void testDurableMessage() {
        //1.准备信息
        Message message = MessageBuilder.withBody("hello,spring".getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .build();
        //2.发送消息
        rabbitTemplate.convertAndSend("simple.queue",message);
    }

}

重启mq后, 发现消息持久化了

RabbitMQ的高级特性(消息可靠性)_第11张图片

4.消息持久化细节处理

会发现队列默认处理时直接持久化的

RabbitMQ的高级特性(消息可靠性)_第12张图片

 然后我们看看发送消息的方法convertAndSend(),看看是怎么处理message的RabbitMQ的高级特性(消息可靠性)_第13张图片

 然后看看Message的处理,也就是convertMessageIfNecessary()方法

发现传入MessageProperties作属性

RabbitMQ的高级特性(消息可靠性)_第14张图片

发现默认就是持久化的 

RabbitMQ的高级特性(消息可靠性)_第15张图片


消费者消息的确认

 MQ三种确认消息模式:

manual:全部执行完,自己手写返回,抛出异常返回nack也得自己手写,在代码中会有切入现象

auto:Spring的aop思想,减少了代码的侵入,Spring确认消息处理后返回ack

none:就是异步思想呗,就跟外卖一样的不管你真正拿到没,mq会假设消费者获取消息后一定会成功处理,所以消息被pulisher投递后就会立马删除,因为他默认就是收到了consumer处理成功的消息;

 我们这里使用auto模式,发现消费者在处理消息时候,(异常出现之前)是Unacked状态,意思就是MQ还没有收到处理成功的回执,然后执行到异常会疯狂自旋(消费者出现异常,消息不断重新入队发送给消费者然后再次异常,导致mq的消息处理up,压力大),一直请求——>好处:没有导致消息的浪费,一直自旋没有返回给MQack的回执,就还是不能删除消息RabbitMQ的高级特性(消息可靠性)_第16张图片

 

我们可以利用Spring的retry机制 

意思就是不返回任何信息给到mq(unacked,ack....),而是自己做重试——>到到达一定的限制在做其他策略

RabbitMQ的高级特性(消息可靠性)_第17张图片

 配置开启retry

spring:
  rabbitmq:
    host: 192.168.184.129 # rabbitMQ的ip地址
    port: 5672 # 端口
    username: itcast
    password: 123321
    virtual-host: /
    listener:
      simple:
        prefetch: 1
        acknowledge-mode: auto
        retry:
          enabled: true
          initial-interval: 1000
          multiplier: 3
          max-attempts: 4
          max-interval: 4

结果:发现最大四次,超过之后拒绝,消息依然失败的话(Retry exhausted),就会删除该消息

RabbitMQ的高级特性(消息可靠性)_第18张图片


消费者消息处理失败策略

开启retry模式若还失败则有三种实现方式——>1.RejectAndDontRequeueRecover:资源耗尽后直接丢弃消息(默认),2.ImmediateRequeueMessage:资源耗尽后,返回nack给到mq,让消息重新入队(自旋),3.RepublishMessageRecover:重试耗尽后,将失败的消息投递到指定的交换机

RabbitMQ的高级特性(消息可靠性)_第19张图片

需要定义一个专门储存失败消息的交换机和队列

 然后重写MessageRecover这个Bean,返回子类RepublishMessageRecover,将消息发送到交换机路由到指定队列中RabbitMQ的高级特性(消息可靠性)_第20张图片



/**
 * @author diao 2022/6/18
 */
@Configuration
public class ErrorMessageConfig {

    @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");
    }

    @Bean
    public MessageRecoverer republishMessage(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate,"error.direct","error");
    }

}

重启消费者服务,再在监听的消息队列中发送消息

发现消费者将消息路由到一个新的交换机中,给到一个消息队列

RabbitMQ的高级特性(消息可靠性)_第21张图片

 error.queue:RabbitMQ的高级特性(消息可靠性)_第22张图片

你可能感兴趣的:(RabbitMQ,rabbitmq,分布式)