RabbitMQ——发布确认高级

发布确认高级

概念

在之前的学习中,我们学习到了单个确认、批量确认、异步确认三种确认方式,也通过实操来比较三种确认方式的性能,其中异步确认是性能最好的。

现在我们考虑以下这种情况,在生产者发送消息的过程中,可能会出现交换机宕机、队列宕机、或者两者一同宕机的情况。这时候消息就石沉大海了,在不适用集群的情况下,来看看如何解决这一问题。

想象一下,假设是小黄寄了一个快递出去,在运输过程中出现在某些状况导致我的快递丢失,这时候快递公司会发一条消息告诉我快递丢失,并与我协商处理方案

在这里插入图片描述

搭建模拟环境

首先我们根据上图来搭建这个例子

编写配置文件

@Configuration
public class ConfirmQueueConfig {
    //声明交换机名称
    public static String CONFIRM_EXCHANGE = "confirm_exchange";
    //声明队列名称
    public static String CONFIRM_QUEUE = "confirm_queue";
    //声明routingKey
    public static String CONFIRM_ROURING_KEY = "confirm_rouring_key";

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

    //声明队列
    @Bean
    public Queue confirmQueue(){
        return new Queue(CONFIRM_QUEUE);
    }

    //绑定交换机和队列
    @Bean
    public Binding confirmQueueBindingExchange(@Qualifier("confirmExchange") DirectExchange confirmExchange,
                                               @Qualifier("confirmQueue") Queue confirmQueue){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROURING_KEY);
    }
}

编写生产者

@GetMapping("/sendConfirmMsg/{message}")
public void sendConfirmMsg(@PathVariable String message){
    log.info("生产者正在发送消息:{}" , message);
    rabbitTemplate.convertAndSend(ConfirmQueueConfig.CONFIRM_EXCHANGE,ConfirmQueueConfig.CONFIRM_ROURING_KEY,message);
}

编写消费者

@Slf4j
@Component
public class ConfirmQueueConsumer {
    @RabbitListener(queues = "confirm_queue")
    public void receiveMsg(Message message){
        log.info("消费者接收到消息:{}", new String(message.getBody()));
    }
}

处理交换机宕机问题

配置RabbitMQ

spring:
  rabbitmq:
    publisher-confirm-type: correlated  # 默认是none,禁止使用发布确认模式,simple单个确认模式

编写回调函数

@Slf4j
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback {
    @Autowired
    RabbitTemplate rabbitTemplate;

    //注入自定义回调函数
    @PostConstruct
    private void init(){
        rabbitTemplate.setConfirmCallback(this);
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        //交换机的回调函数
        if (ack){
            //接收成功
            log.info("交换机成功接收到id为{}的消息",correlationData.getId());
        }else {
            //接受失败
            log.error("交换机未接收到id为{}的消息,失败原因:{}",correlationData.getId(),cause);
        }
    }
}

修改生产者代码

发送消息给一个不存在的虚拟机,看是否能回复生产者

@GetMapping("/sendConfirmMsg/{message}")
public void sendConfirmMsg(@PathVariable String message){
    log.info("生产者正在发送消息:{}" , message);
    CorrelationData correlationData1 = new CorrelationData("1");
    rabbitTemplate.convertAndSend(ConfirmQueueConfig.CONFIRM_EXCHANGE,ConfirmQueueConfig.CONFIRM_ROURING_KEY,message,correlationData1);

    CorrelationData correlationData2 = new CorrelationData("2");
    //模拟交换机宕机,随便搞一个不存在的交换机
    rabbitTemplate.convertAndSend(ConfirmQueueConfig.CONFIRM_EXCHANGE + "2",ConfirmQueueConfig.CONFIRM_ROURING_KEY,message,correlationData1);
}

测试

启动测试,可以看到控制台确实能收到失败的消息,到此我们就解决了交换机宕机的问题

在这里插入图片描述

处理队列宕机问题

配置RabbitMQ

spring:
  rabbitmq:
    publisher-returns: true

编写回调函数

@Slf4j
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {
    @Autowired
    RabbitTemplate rabbitTemplate;

    //注入自定义回调函数
    @PostConstruct
    private void init(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        //交换机的回调函数
        if (ack){
            //接收成功
            log.info("交换机成功接收到id为{}的消息",correlationData.getId());
        }else {
            //接受失败
            log.error("交换机未接收到id为{}的消息,失败原因:{}",correlationData.getId(),cause);
        }
    }

    //当消息不可到达目的地时,回退
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        String msg = new String(returned.getMessage().getBody());
        log.error("消息:{}被服务器退回,退回原因:{}, 交换机是:{}, 路由 key:{}",
                msg,returned.getReplyText(),returned.getExchange(),returned.getRoutingKey());
    }
}

修改生产者代码

@GetMapping("/sendConfirmMsg/{message}")
public void sendConfirmMsg(@PathVariable String message){
    log.info("生产者正在发送消息:{}" , message);
    CorrelationData correlationData1 = new CorrelationData("1");
    rabbitTemplate.convertAndSend(ConfirmQueueConfig.CONFIRM_EXCHANGE,ConfirmQueueConfig.CONFIRM_ROURING_KEY,message,correlationData1);

    CorrelationData correlationData2 = new CorrelationData("2");
    //模拟队列宕机,写一个不存在的routigKey
    rabbitTemplate.convertAndSend(ConfirmQueueConfig.CONFIRM_EXCHANGE,ConfirmQueueConfig.CONFIRM_ROURING_KEY + "2",message,correlationData2);
}

测试

观察控制台输出,可以发现由于队列找不到的消息返回到了生产者

在这里插入图片描述

备份交换机

队列宕机之后,我们也需要通知人员来进行处理,这时候就用到了备份交换机,我们设置当消息到达不了目的地时,转发到备份交换机,通过备份队列来保存数据,通过报警队列告诉报警消费者

在这里插入图片描述

修改配置文件

在原先的配置文件上,我们需要新增备份交换机、备份队列、报警队列

@Configuration
public class ConfirmQueueConfig {
    //声明交换机名称
    public static String CONFIRM_EXCHANGE = "confirm_exchange";
    //声明队列名称
    public static String CONFIRM_QUEUE = "confirm_queue";
    //声明routingKey
    public static String CONFIRM_ROURING_KEY = "confirm_rouring_key";

    //声明备份交换机名称
    public static String ALTERNATE_EXCHANGE = "alternate_exchange";
    //声明备份队列名称
    public static String ALTERNATE_QUEUE = "alternate_queue";
    //声明报警队列名称
    public static String WARNING_QUEUE = "warning_queue";

    //声明备份交换机
    @Bean
    public FanoutExchange alternateExchange(){
        return new FanoutExchange(ALTERNATE_EXCHANGE);
    }

    //声明备份队列
    @Bean
    public Queue alternateQueue(){
        return new Queue(ALTERNATE_QUEUE);
    }

    //声明报警队列
    @Bean
    public Queue warningQueue(){
        return new Queue(WARNING_QUEUE);
    }

    //绑定备份队列和备份交换机
    @Bean
    public Binding alternateQueueBindingAlternateExchange(@Qualifier("alternateQueue") Queue alternateQueue,
                                                          @Qualifier("alternateExchange") FanoutExchange alternateExchange){
        return BindingBuilder.bind(alternateQueue).to(alternateExchange);
    }

    //绑定报警队列和备份交换机
    @Bean
    public Binding warningQueueBindingAlternateExchange(@Qualifier("warningQueue") Queue warningQueue,
                                                          @Qualifier("alternateExchange") FanoutExchange alternateExchange){
        return BindingBuilder.bind(warningQueue).to(alternateExchange);
    }

    //声明交换机
    @Bean
    public DirectExchange confirmExchange(){
        ExchangeBuilder exchangeBuilder = ExchangeBuilder.directExchange(CONFIRM_EXCHANGE).withArgument("alternate-exchange", ALTERNATE_EXCHANGE);
        return (DirectExchange) exchangeBuilder.build();
    }

    //声明队列
    @Bean
    public Queue confirmQueue(){
        return new Queue(CONFIRM_QUEUE);
    }

    //绑定交换机和队列
    @Bean
    public Binding confirmQueueBindingExchange(@Qualifier("confirmExchange") DirectExchange confirmExchange,
                                               @Qualifier("confirmQueue") Queue confirmQueue){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROURING_KEY);
    }
}

新增报警消费者

@Slf4j
@Component
public class WarningQueueConsumer {
    @RabbitListener(queues = "warning_queue")
    public void receiveMsg(Message message){
        log.error("报警发现不可路由消息:{}", new String(message.getBody()));
    }
}

测试

通过控制台输出,我们可以看到报警消费者确实接收到了报警消息。

这里需要注意的是:当消息回退和备份交换机同时存在时,备份交换机的优先级高

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值