发布确认高级

发布确认实现

之前提到的发布确认机制,即 channel.addConfirmListener()异步发布确认)是 rabbitmq 原生 Java 客户端实现的消息发布确认回调,现在使用的是 springboot,可以通过实现 Spring AMQP 提供的 ConfirmCallback 接口,用于处理消息的确认回调,该接口是异步接口

复习概念

由于某种原因(rabbitmq 重启、宕机…),导致生产者消息投递到交换机或者队列之前,消息就已经丢失了,此时需要手动处理和恢复,处理方案即使用发布确认机制,实现对消息发送结果的实时监控和处理

交换机挂掉

交换机接收不到消息时进行的处理

确认机制方案

在这里插入图片描述

代码架构图

在这里插入图片描述

配置文件

在配置文件中需设置 publisher-confirm-typecorrelated

spring:
  rabbitmq:
    port: 5672
    publisher-confirm-type: correlated
  • none:默认值,表示禁用发布确认模式,是默认值
  • correlated:发布消息成功到交换器后会触发回调方法
  • simple:有 2 个效果,如下:
    • 跟 correlated 一样会触发回调方法
    • 发布消息成功后可以使用 rabbitTemplate 调用 waitForConfirms()waitForConfirmsOrDie() 来等待消息的发布确认结果,这两个方法都会阻塞当前线程,是同步操作,如果使用 waitForConfirmsOrDie() 方法,并返回 false,它将会关闭通道,导致后续无法发送消息到 RabbitMQ

声明

@Configuration
public class ConfirmConfig {

    // 声明交换机
    @Bean
    public DirectExchange confirmExchange() {
        return new DirectExchange("confirm_xchange");
    }

    // 声明队列
    @Bean
    public Queue confirmQueue() {
        return QueueBuilder.durable("confirm.queue").build();
    }

    // 绑定
    @Bean
    public Binding queueBindingExchange(
            @Qualifier("confirmExchange") DirectExchange confirmExchange,
            @Qualifier("confirmQueue") Queue confirmQueue) {

        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with("key1");

    }

}

生产者

@RestController
@RequestMapping("/confirm")
public class ProducerController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    private static final Logger log = LoggerFactory.getLogger(ProducerController.class);

    // 发消息
    @GetMapping("/sendMessage/{message}")
    public void sendMessage(@PathVariable String message) {
        rabbitTemplate.convertAndSend("confirm_exchange",
                "key1", message);
        log.info("发送消息内容:{}", message);
    }
}

消费者

@Component
public class Consumer {

    private static final Logger log = LoggerFactory.getLogger(Consumer.class);

    @RabbitListener(queues = "confirm.queue")
    public void receiveConfirmMessage(Message message) {
        String msg = new String(message.getBody());
        log.info("队列 confirm.queue 的消息是:{}", msg);
    }
}

回调接口

以上代码是正常的发送消息和消费消息,现在模拟交换机挂了,使用回调接口,该接口作用如下:

  • 当生产者发消息给交换机后成功了会触发回调函数
  • 当生产者发消息给交换机后失败了也会触发回调函数

代码如下:

@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback {

    private static final Logger log = LoggerFactory.getLogger(MyCallBack.class);
    /**
     * 交换机确认消息的回调方法
     * 什么时候会回调:
     * 1.发消息,交换机成功接收到了
     *     1.1 correlationData 保存回调消息的 ID 及相关信息
     *     1.2 交换机是否收到消息,true
     *     1.3 失败原因,null
     * 2.发消息,交换机接收失败了
     *     2.1 correlationData 保存回调消息的 ID 及相关信息
     *     2.2 交换机是否收到消息,false
     *     2.3 失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack) {
            log.info("交换机成功收到 ID 为 {}的消息", id);
        } else {
            log.info("交换机还未收到 ID 为 {}的消息,原因是:{}", id, cause);
        }
    }

    // 本类只是单纯实现了 RabbitTemplate 的内部接口,并不会
    // 作用于 RabbitTemplate,需注入到 RabbitTemplate 中
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * @PostConstruct 作用如下:
     * Spring 容器实例化 Bean 并完成依赖注入后立即执行该方法
     */
    @PostConstruct
    public void init(){
        // 注入
        rabbitTemplate.setConfirmCallback(this);
    }
}

该回调接口的 correlationData 参数需要生产者发送消息时发送,之前的生产者并没有发送 CorrelationData 对象,修改后的生产者代码如下:

@RequestMapping("/confirm")
public class ProducerController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    private static final Logger log = LoggerFactory.getLogger(ProducerController.class);

    // 发消息
    @GetMapping("/sendMessage/{message}")
    public void sendMessage(@PathVariable String message) {
        CorrelationData correlationData = new CorrelationData("1");
		
        // 故意写错交换机的名字,模拟交换机挂了
        rabbitTemplate.convertAndSend("confirm_exchange666", 
                "key1", message, correlationData);

        log.info("发送消息内容:{}", message);
    }
}

队列挂掉

mandatory

仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如果发现该消息不可路由,那么消息会被直接丢弃,此时生产者并不知道消息已经被丢弃,通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者

配置文件

添加 publisher-returnstrue,表示开启消息回退,当消息无法被路由到合适的队列时,RabbitMQ 会将该消息退回给生产者

spring:
  rabbitmq:
    port: 5672
    publisher-confirm-type: correlated
    publisher-returns: true

回退接口

@Component
public class MyCallBack2 implements RabbitTemplate.ReturnCallback{

    private static final Logger log = LoggerFactory.getLogger(MyCallBack2.class);

    // 本类只是单纯实现了 RabbitTemplate 的内部接口,并不会
    // 作用于 RabbitTemplate,需注入到 RabbitTemplate 中
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * @PostConstruct 作用如下:
     * Spring 容器实例化 Bean 并完成依赖注入后立即执行该方法
     */
    @PostConstruct
    public void init(){
        // 注入
        rabbitTemplate.setReturnCallback(this);
        /**
         * 设置 mandatory
         * true:交换机无法将消息进行路由时,会将该消息返回给生产者
         * false:如果发现消息无法进行路由,则直接丢弃
         */
        rabbitTemplate.setMandatory(true);
    }

    // 当消息不可达目的地时才进行回退
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText,
                                String exchange, String routingKey) {

        log.error("消息:{},被交换机{}退回,退回原因是{},路由key是{}",
                new String(message.getBody()), exchange, replyText, routingKey);

    }
}

备份交换机

有了 mandatory 参数和回退接口后,我们获得了对投递消息失败后的感知能力,能在生产者的消息无法被投递时发现并处理,但就需要在生产者中添加处理这些被退回的消息的逻辑,从而增加了生产者的复杂度

如果既不想丢失消息,又不想增加生产者的复杂性,可以使用备份交换机,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout

代码架构图

在这里插入图片描述

在备份交换机中绑定 1 个备份队列和 1 个报警队列,用独立的消费者对报警队列进行监测和报警

声明

在之前代码中新增 1 个备份交换机和 2 个队列,代码如下:

@Configuration
public class ConfirmConfig {

    // 声明交换机
    @Bean
    public DirectExchange confirmExchange() {
        return ExchangeBuilder.directExchange("confirm_exchange").durable(true)
        .withArgument("alternate-exchange", "backup.exchange") // 绑定备份交换机
        .build();
    }

    // 声明队列
    @Bean
    public Queue confirmQueue() {
        return QueueBuilder.durable("confirm.queue").build();
    }

    // 绑定
    @Bean
    public Binding queueBindingExchange(
            @Qualifier("confirmExchange") DirectExchange confirmExchange,
            @Qualifier("confirmQueue") Queue confirmQueue) {

        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with("key1");

    }

    // 声明备份交换机
    @Bean
    public FanoutExchange backupExchange() {
        return new FanoutExchange("backup.exchange");
    }

    // 声明备份队列
    @Bean
    public Queue backupQueue() {
        return QueueBuilder.durable("backup.queue").build();
    }

    // 声明报警队列
    @Bean
    public Queue warningQueue() {
        return QueueBuilder.durable("warning.queue").build();
    }

    // 绑定备份交换机和备份队列
    @Bean
    public Binding backupQueueBindingBackup(
            @Qualifier("backupExchange") FanoutExchange backupExchange,
            @Qualifier("backupQueue") Queue backupQueue) {

        return BindingBuilder.bind(backupQueue).to(backupExchange);

    }

    // 绑定备份交换机和报警队列
    @Bean
    public Binding backupQueueBindingWarning(
            @Qualifier("backupExchange") FanoutExchange backupExchange,
            @Qualifier("warningQueue") Queue waringQueue) {

        return BindingBuilder.bind(waringQueue).to(backupExchange);

    }
}
消费者
@Component
public class WarningConsumer {

    private static final Logger log = LoggerFactory.getLogger(WarningConsumer.class);

    // 接收报警消息
    @RabbitListener(queues = "warning.queue")
    public void receiveWarningMsg(Message message){
        String msg = new String(message.getBody());
        log.error("报警发现不可路由消息:{}", msg);
    }
}

mandatory 参数与备份交换机可以一起使用时,回退接口不会执行,备份交换机的优先级高

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值