RabbitMQ学习—day7—发布确认高级

目录

介绍

发布确认SpringBoot版本

回退消息

备份交换机


 

介绍

在生产环境中由于一些不明原因,导致 rabbitmq重启,在RabbitMQ重启期间生产者消息投递失败,导致消息丢失,需要手动处理和恢复。于是,我们开始思考,如何才能进行RabbitMQ的消息可靠投递呢?特别是在这样比较极端的情况,RabbitMQ集群不可用的时候,无法投递的消息该如何处理呢:

发布确认SpringBoot版本

确认机制方案

实现

  • 配置文件:添加
spring.rabbitmq.publisher-confirm-type = correlated
  • 配置类
// 配置类:发布确认(高级)
@Configuration
public class ConfirmConfig {

    //交换机
    public static final String CONFIRM_EXCHANGE_NAME = "confirm_exchange";
    //队列
    public static final String CONFIRM_QUEUE_NAME = "confirm_queue";
    //RoutingKey
    public static final String CONFIRM_routing_key = "key1";

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

    @Bean
    public Queue confirmQueue(){
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    //绑定
    @Bean
    public Binding queueBindingExchange(@Qualifier("confirmQueue") Queue confirmQueue,
                                        @Qualifier("confirmExchange")DirectExchange confirmExchange){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_routing_key);
    }
}

生产者

 // 开始发消息 测试确认
 @RestController
 @Slf4j
 @RequestMapping("/confirm")
 public class ProducerController {

 @Autowired
 private RabbitTemplate rabbitTemplate;

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

消费者

 // 接收消息
 @Slf4j
 @Component
 public class Consumer {

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

回调接口

 @Component
 @Slf4j
 public class MyCallBack implements RabbitTemplate.ConfirmCallback {

 @Autowired
 private RabbitTemplate rabbitTemplate;

 @PostConstruct
 public void init(){
     //注入
     rabbitTemplate.setConfirmCallback(this);
 }

 /*
 * 交换机确认回调方法,发消息后,交换机接收到了就回调
 *   1.1 correlationData:保存回调消息的ID及相关信息
 *   1.2 b:交换机收到消息,为true
 *   1.3 s:失败原因,成功为null
 *
 * 发消息,交换机接受失败,也回调
 *   2.1 correlationData:保存回调消息的ID及相关信息
 *   2.2 b:交换机没收到消息,为false
 *   2.3 s:失败的原因
 *
  * */

 @Override
 public void confirm(CorrelationData correlationData, boolean b, String s) {
     String id = correlationData!=null ? correlationData.getId():"";
     if (b){
         log.info("交换机已经收到ID为:{}的信息",id);
     }else {
         log.info("交换机还未收到ID为:{}的消息,由于原因:{}",id,s);
     }
   }
 }

配置文件及消息发送方

  1. NONE:禁用发布确认模式,是默认值
  2. CORRELATED:发布消息成功到交换器后会触发回调方法
  3. SIMPLE:经测试有两种效果,其一效果和CORRELATED值一样会触发回调方法,其二在发布消息成功后使用rabbitTemplate调用waitForConfirms,或 waitForConfirmsOrDie方法等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDiea方法如果返回false则会关闭channel,则接下来无法发送消息到broker
spring.rabbitmq.publisher-confirm-type = correlated

但此时出了问题,如果交换机出现问题可以回调接口,但是如果队列出问题无法回调接口

回退消息

也就是交换机发给队列时,队列收不到消息时进行回退
在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的。那么如何让无法被路由的消息帮我想办法处理一下?最起码通知我一声,我好自己处理啊。通过设置mandatory参数可以在当消息传递过程中不可达目的地时将消息返回给生产者。

配置文件

spring.rabbitmq.publisher-returns=true



回退接口

 @Component
 @Slf4j
 public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {

 @Autowired
 private RabbitTemplate rabbitTemplate;

 @PostConstruct
 public void init(){
     //注入
     rabbitTemplate.setConfirmCallback(this);
     rabbitTemplate.setReturnsCallback(this);
 }

 /*
 * 交换机确认回调方法,发消息后,交换机接收到了就回调
 *   1.1 correlationData:保存回调消息的ID及相关信息
 *   1.2 b:交换机收到消息,为true
 *   1.3 s:失败原因,成功为null
 *
 * 发消息,交换机接受失败,也回调
 *   2.1 correlationData:保存回调消息的ID及相关信息
 *   2.2 b:交换机没收到消息,为false
 *   2.3 s:失败的原因
 *
  * */

 @Override
 public void confirm(CorrelationData correlationData, boolean b, String s) {
     String id = correlationData!=null ? correlationData.getId():"";
     if (b){
         log.info("交换机已经收到ID为:{}的信息",id);
     }else {
         log.info("交换机还未收到ID为:{}的消息,由于原因:{}",id,s);
     }
 }

 //可以在当消息传递过程中不可达目的的时将消息返回给生产者
 //只有不可达目的地的时候才可回退
 @Override
 public void returnedMessage(ReturnedMessage returnedMessage) {
     log.error("消息{},被交换机{}退回,退回的原因:{},路由Key:{}",
             new String(returnedMessage.getMessage().getBody())
             , returnedMessage.getExchange()
             , returnedMessage.getReplyText()
             , returnedMessage.getRoutingKey());
   }
 }


发布测试 

// 开始发消息 测试确认
 @RestController
 @Slf4j
 @RequestMapping("/confirm")
 public class ProducerController {

 @Autowired
 private RabbitTemplate rabbitTemplate;

 //发消息
 @GetMapping("/sendMessage/{message}")
 public void sendMessage(@PathVariable String message){
     CorrelationData correlationData = new CorrelationData("1");
     rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME
             ,ConfirmConfig.CONFIRM_routing_key
             ,message+"key1",correlationData);
     log.info("发送消息内容:{}",message+"key1");

     CorrelationData correlationData2 = new CorrelationData("2");
     rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME
             ,ConfirmConfig.CONFIRM_routing_key+"2"
             ,message+"key12",correlationData2);
     log.info("发送消息内容:{}",message+"key12");
 }
}


效果:实现回退消息成功!

备份交换机

有了mandatory 参数和回退消息,我们获得了对无法投递消息的感知能力,有机会在生产者的消息无法被投递时发现并处理。但有时候,我们并不知道该如何处理这些无法路由的消息,最多打个日志,然后触发报警,再来手动处理。而通过日志来处理这些无法路由的消息是很不优雅的做法,特别是当生产者所在的服务有多台机器的时候,手动复制日志会更加麻烦而且容易出错。而且设置mandatory参数会增加生产者的复杂性,需要添加处理这些被退回的消息的逻辑。如果既不想丢失消息,又不想增加生产者的复杂性,该怎么做呢?前面在设置死信队列的文章中,我们提到,可以为队列设置死信交换机来存储那些处理失败的消息,可是这些不可路由消息根本没有机会进入到队列,因此无法使用死信队列来保存消息。在RabbitMQ.中,有一种备份交换机的机制存在,可以很好的应对这个问题。什么是备份交换机呢?备份交换机可以理解为 RabbitMQ中交换机的“备胎”,当我们为某一个交换机声明一个对应的备份交换机时,就是为它创建一个备胎,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为Fanout,这样就能把所有消息都投递到与其绑定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都进入这个队列了。当然,我们还可以建立一个报警队列,用独立的消费者来进行监测和报警。

配置类

// 配置类:发布确认(高级)
@Configuration
public class ConfirmConfig {

    //交换机
    public static final String CONFIRM_EXCHANGE_NAME = "confirm_exchange";
    //队列
    public static final String CONFIRM_QUEUE_NAME = "confirm_queue";
    //RoutingKey
    public static final String CONFIRM_routing_key = "key1";
    //备份交换机
    public static final String BACKUP_EXCHANGE_NAME = "backup_exchange";
    //备份队列
    public static final String BACKUP_QUEUE_NAME = "backup_queue";
    //报警队列
    public static final String WARNING_QUEUE_NAME = "warning_queue";

    //声明交换机
    @Bean
    public DirectExchange confirmExchange(){
        return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME).durable(true)
                .withArgument("alternate-exchange",BACKUP_EXCHANGE_NAME).build();
    }

    @Bean
    public Queue confirmQueue(){
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    //绑定
    @Bean
    public Binding queueBindingExchange(@Qualifier("confirmQueue") Queue confirmQueue,
                                        @Qualifier("confirmExchange")DirectExchange confirmExchange){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_routing_key);
    }

    //备份交换机
    @Bean
    public FanoutExchange backupExchange(){
        return new FanoutExchange(BACKUP_EXCHANGE_NAME);
    }

    //备份队列
    @Bean
    public Queue backupQueue(){
        return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
    }

    //报警队列
    @Bean
    public Queue warningQueue(){
        return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
    }

    @Bean
    public Binding backupQueueBindingBackupExchange(@Qualifier("backupQueue") Queue backupQueue,
                                                    @Qualifier("backupExchange") FanoutExchange backupExchange){
        return BindingBuilder.bind(backupQueue).to(backupExchange);
    }

    @Bean
    public Binding warningQueueBindingBackupExchange(@Qualifier("warningQueue") Queue backupQueue,
                                                    @Qualifier("backupExchange") FanoutExchange backupExchange){
        return BindingBuilder.bind(backupQueue).to(backupExchange);
    }
}

消费者

// 配置类:发布确认(高级)
@Configuration
public class ConfirmConfig {

    //交换机
    public static final String CONFIRM_EXCHANGE_NAME = "confirm_exchange";
    //队列
    public static final String CONFIRM_QUEUE_NAME = "confirm_queue";
    //RoutingKey
    public static final String CONFIRM_routing_key = "key1";
    //备份交换机
    public static final String BACKUP_EXCHANGE_NAME = "backup_exchange";
    //备份队列
    public static final String BACKUP_QUEUE_NAME = "backup_queue";
    //报警队列
    public static final String WARNING_QUEUE_NAME = "warning_queue";

    //声明交换机
    @Bean
    public DirectExchange confirmExchange(){
        return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME).durable(true)
                .withArgument("alternate-exchange",BACKUP_EXCHANGE_NAME).build();
    }

    @Bean
    public Queue confirmQueue(){
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    //绑定
    @Bean
    public Binding queueBindingExchange(@Qualifier("confirmQueue") Queue confirmQueue,
                                        @Qualifier("confirmExchange")DirectExchange confirmExchange){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_routing_key);
    }

    //备份交换机
    @Bean
    public FanoutExchange backupExchange(){
        return new FanoutExchange(BACKUP_EXCHANGE_NAME);
    }

    //备份队列
    @Bean
    public Queue backupQueue(){
        return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
    }

    //报警队列
    @Bean
    public Queue warningQueue(){
        return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
    }

    @Bean
    public Binding backupQueueBindingBackupExchange(@Qualifier("backupQueue") Queue backupQueue,
                                                    @Qualifier("backupExchange") FanoutExchange backupExchange){
        return BindingBuilder.bind(backupQueue).to(backupExchange);
    }

    @Bean
    public Binding warningQueueBindingBackupExchange(@Qualifier("warningQueue") Queue backupQueue,
                                                    @Qualifier("backupExchange") FanoutExchange backupExchange){
        return BindingBuilder.bind(backupQueue).to(backupExchange);
    }
}

 效果

  • mandatory参数与备份交换机可以一起使用的时候,如果两者同时开启,消息究竟何去何从?谁优先级高,经过上面结果显示答案是备份交换机优先级高

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值