RabbiitMQ高级

1 常见问题

  • 消息可靠性问题
  • 延迟消息问题
  • 消息堆积问题
  • 高可用问题

2 消息可靠性

2.1 消息可靠性问题

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

  • 发送时丢失
    • 生产者发送的消息未送达exchange
    • 消息到达exchange后未到达queue
  • MQ宕机,queue将消息丢失
  • consumer接受到消息未消费宕机

2.2 生产者消息确认

2.2.1 生产者确认机制

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

  • publisher-confirm,发送者确认
    消息成功投递到交换机,返回ack
    消息未投递到交换机,返回nack
  • publisher-return,发送者回执
    消息投递到交换机了,但是没有路由到队列。返回ack,及路由失败原因
    RabbiitMQ高级_第1张图片

2.2.2 实现生产者确认

1 在 publisher 的 yaml 中添加配置

# 开启 publisher-confirm
    # simple: 同步等待回调
    # correlated: 异步回调,定义ConfirmCallback,MQ返回结果会回调这个ConfirmCallback
    publisher-confirm-type: correlated
    # 开启 publisher-return 功能,同样是基于callback机制,不过是定义ReturnCallback
    publisher-returns: true
    # 定义消息路由失败时的策略。true: 则回调ReturnCallback; false: 则直接丢弃消息
    template:
      mandatory: true

2 ReturnCallback

每个RabbitTemplate 只能配置一个ReturnCallback(全局共用)

@Configuration
@Slf4j
public class CommonConfig implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        rabbitTemplate.setReturnCallback((message, i, s, s1, s2) -> {
            log.info("ReturnCallback...");
            log.info("消息: {}", message);
            log.info("应答码: {}", i);
            log.info("失败原因: {}", s);
            log.info("路由key: {}", s1);
            log.info("消息: {}", s2);
        });
    }
}

3 ConfirmCallback

// 消息体
String message = "hello rabbitmq";
// 消息id 消息配上唯一的id,为了区分消息
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
// 添加 callback
correlationData.getFuture().addCallback(confirm -> {
    // 成功回调
    if (confirm.isAck()) {
        // ack
        log.info("消息成功投递到交换机! {}", correlationData.getId());
    } else {
        // nack
        log.info("消息投递到交换机失败! {}", correlationData.getId());
    }
}, throwable -> {
    // 失败回调
    log.info("消息发送失败: {}", throwable.getMessage());
});
// 发送消息
rabbitTemplate.convertAndSend("amq.exchange", "simple", message, correlationData);

2.3 消息持久化

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

2.3.1 交换机持久化

@Bean
public DirectExchange durableExchange() {
     // 三个参数: 交换机名称,是否持久化,当没有queue与其绑定时自动删除
     return new DirectExchange("durable.exchange", true, false);
 }

2.3.2 队列持久化

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

2.3.3 消息持久化

Message msg = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT) //持久化
                .build();

2.4 消费者消息确认

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

  • manual: 手动ack,需要在业务代码结束后,调用api发送ack
  • auto:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack
  • none: 关闭ack,MQ假定消费者获取消息必定成功处理,投递消息后立马删除

2.4.1 yaml配置

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1
        acknowledge-mode: auto

2.5 消费失败重试机制

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

2.5.1 spring retry机制 (重试模式)

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

2.5.2 MessageRecover

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

  • RejectAndDontRequeueRecover: 重试失败后,直接reject,丢弃消息(默认)
  • ImmediateRequeueMessageRecover: 重试失败后,返回nack,消息重新入队
  • RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机
    RabbiitMQ高级_第2张图片

2.5.3 RepublishMessageRecoverer模式

1 定义失败消息的交换机、队列及其绑定关系

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

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

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

2 定义RepublishMessageRecoverer

@Bean
public RepublishMessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
    return new RepublishMessageRecoverer(rabbitTemplate, "error.exchange", "error");
}

3 延迟消息问题

3.1 死信交换机

3.1.1 死信

当一个队列中的消息满足下列情况之一时,可以成为死信

  • 消费者使用 basic.reject 或 basic.nack 声明消费失败,并且消息的 requeue 参数设置为 false
  • 消息是一个过期消息,超时无人消费
  • 要投递的队列消息堆积满了,最早的消息可能成为死信

3.1.2 死信交换机

如果一个队列配置了 dead-letter-exchange 属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为死信交换机(Dead Letter Exchange,简称DLX)
RabbiitMQ高级_第3张图片

3.2 TTL

TTL,也就是Time-To-Live。如果一个队列中的消息TTL结束仍未消费,则会变成死信,ttl超时会分为两种情况:

  • 消息所在的队列设置了存活时间
  • 消息本身设置了存活时间
    RabbiitMQ高级_第4张图片

接收方

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "dl.queue", durable = "true"),
            exchange = @Exchange(name="dl.direct"),
            key = "dl"
    ))
    public void listenDlQueue(String msg){
        log.info("接收到 dl。queue的延迟消息:{}", msg);
    }

发送方:

    @Bean
    public DirectExchange ttlExchange() {
        return new DirectExchange("ttl.direct");
    }

    @Bean
    public Queue ttlQueue() {
        return QueueBuilder.durable("ttl.queue") // 指定队列名称,并持久化
                .ttl(10000) // 设置队列的超时时间, 10秒
                .deadLetterExchange("dl.direct") // 指定死信交换机
                .deadLetterRoutingKey("dl") // 指定死信 RoutingKey
                .build();
    }

    @Bean
    public Binding ttlBinding() {
        return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("ttl");
    }
    RabbitTemplate rabbitTemplate = run.getBean(RabbitTemplate.class);
    Message msg = MessageBuilder.withBody("ttl message".getBytes(StandardCharsets.UTF_8))
            .setDeliveryMode(MessageDeliveryMode.PERSISTENT) //持久化
            .setExpiration("5000") // 消息失效时间
            .build();
    rabbitTemplate.convertAndSend("ttl.direct","ttl", msg);

3.3 延迟队列

利用 TTL 结合死信交换机,我们实现了消息发出后,消费者延迟收到消息的效果。这种消息模式被称为延迟队列(Delay Queue)模式。
延迟丢列的使用场景包括:

  • 延迟发送短信
  • 用户下单,如果用户15分钟内未支付,则自动取消
  • 预约工作会议,20分钟后自动通知所有参会人员

3.3.1 延迟队列插件

延迟队列插件

3.3.2 安装

你可能感兴趣的:(rabbitmq,java,java-rabbitmq,rabbitmq)