RabbitMQ存在的问题及解决方法

一、消息可靠性

RabbitMQ存在的问题及解决方法_第1张图片

1.由RabbitMQ发送与消费消息的模型可知消息丢失发生在以下几种情况。
    1、生产者发送消息未到达交换机
    2、消息到达交换机,没有正确路由到队列
    3、MQ宕机,队列中的消息不见了
    4、消费者收到消息,还没消费,消费者宕机

2.如何保证消息不丢失?

生产者确认机制

(1)配置文件

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

(2)定义ConfirmCallback

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

  @Test
    public void confirmAndReturnTest() throws Exception {
        String uuid = UUID.randomUUID().toString().replace("-", "");
        CorrelationData correlationData = new CorrelationData(uuid);
        correlationData.getFuture().addCallback(result -> {
            //没出异常,走这里
            if(result.isAck()) {
                log.warn("666---> 消息正确到达交换机:" + correlationData.getId());
            } else {
                log.error("发送失败了,没有到达交换机:"+ correlationData.getId());
                //记录日志,重发
                log.info("发送失败,重新发送消息--->222");
                //消息发送失败回调的方法,一个消息对应一个
            }
        }, ex -> {
            //出异常,走这里
            log.error("发送消息出问题了:" + ex.getMessage() + " --- " + correlationData.getId());
            //记录日志,重发
            log.info("出异常,重新发送消息--->111");
        });

(3)定义Return回调

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

@Slf4j
@Configuration
public class CommonConfig  implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RabbitTemplate bean = applicationContext.getBean(RabbitTemplate.class);
        bean.setReturnCallback((message,  replyCode, replyText, exchange, routingKey) -> {
            //投递失败,记录口志
            log.error("消息发送失败,应答码:{},失败文本原因:{},交换机:{} ,路由键: {},消息:{}",
                    replyCode, replyText, exchange, routingKey, message.toString());
            log.warn("重发消息");
            //rabbitTemplate.convertAndSend(exchange, routingKey, message.getBody());
        });
    }
}

持久化机制(交换机、队列、消息进行持久化)

消费者ack机制

(1)none:只要消息到达消费者,Spring直接返回ack给MQ

(2)消费者开启手动ack,或者自动ack + 重试耗尽的失败策略,定义错误交换机队列,后期通过人工进行干预,设置了重试次数。如果一直失败的话,肯定有问题了。记录日志,人工干预。

(3)auto:自动ack。消费消息不出异常,返回ack给MQ。消费消息出异常了,返回nack,把消息重回队列。

配置如下:

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true  #开启消费者失败重试
          initial-interval: 1000  #初始的失败等待时长为1秒
          multiplier: 2  #失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 3   #最大重试次数
          stateless: true    #true无状态;false有状态。如果业务中包含事务,这里改为false

RabbitMQ存在的问题及解决方法_第2张图片

二、消息重复消费问题


   (1) 消费者的操作是否是一个幂等性操作? 什么是幂等性?
            多次执行同一个操作,最终的结果是一样的。
  (2)对于非幂等性操作,多次消费消息,会造成数据一致性的问题,所以要保证重复消费消息的问题,如何解决?

RabbitMQ存在的问题及解决方法_第3张图片

三、消息积压问题

生产者生产消息的速度 远高于 消费者消费消息的速度?于是就会造成消息积压在MQ。

假设是消费者引起的,解决消费者代码或者临时开启多个消费者,来以倍速消费积压的消息。当积压的消息消费的差不多的情况,关闭临时消费者。

四、死信交换机

在RabbitMQ中,没有延迟队列的功能。可以使用 TTL + 死信队列 的方式实现延迟队列。应用场景(延迟发送短信,付款时间等等)

原理图如下:

RabbitMQ存在的问题及解决方法_第4张图片

通过DelayExchange插件实现延迟队列

RabbitMQ存在的问题及解决方法_第5张图片

生产者发送消息:

RabbitMQ存在的问题及解决方法_第6张图片

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