RabbitMQ(三)-- 消息可靠性

RabbitMQ消息的可靠性投递主要两种实现:
1、通过实现消费的重试机制,通过@Retryable来实现重试,可以设置重试次数和重试频率;
2、生产端实现消息可靠性投递。

两种方法消费端都可能收到重复消息,要求消费端必须实现幂等性消费。

消息的可靠投递

生产端

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式

  • confirm 确认模式

  • return 退回模式

消息投递到exchange的确认模式

rabbitmq的消息投递的过程为:

producer ——> rabbitmq broker cluster ——> exchange ——> queue ——> consumer

  • 生产端发送消息到rabbitmq broker cluster后,异步接受从rabbitmq返回的ack确认信息

  • 生产端收到返回的ack确认消息后,根据ack是true还是false,调用confirmCallback接口进行处理

1、改yml

spring: 
  #rabbitmq 连接配置 
  rabbitmq: 
    publisher-confirm-type: correlated # 开启confirm确认模式

2、实现confirm方法

实现ConfirmCallback接口中的confirm方法,消息只要被 rabbitmq broker接收到就会触ConfirmCallback 回调,ack为true表示消息发送成功,ack为false表示消息发送失败

package com.rabbitmq.config; 
import org.springframework.amqp.rabbit.connection.CorrelationData;

import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Component; 

/*** 实现ConfirmCallback接口 */
@Component 
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback { 

  /** 
   * @param correlationData 相关配置信息 
   * @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败 
   * @param cause 失败原因 
   */
   @Override 
   public void confirm(CorrelationData correlationData, boolean ack, String cause) { 
	  if (ack) { 
		//接收成功 
		System.out.println("成功发送到交换机<===>"); 
	  } else { 
		//接收失败 
		System.out.println("失败原因:===>" + cause); 
		//TODO 做一些处理:消息再次发送等等 
	  } 
    } 
}

3、测试

定义 Exchange 和 Queue

定义交换机 confirmTestExchange 和队列 confirm_test_queue ,并将队列绑定在交换机上。

package com.rabbitmq.config; 
import org.springframework.amqp.core.Binding; 
import org.springframework.amqp.core.BindingBuilder; 
import org.springframework.amqp.core.FanoutExchange; 
import org.springframework.amqp.core.Queue; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
/*** 
  *队列与交换机绑定 
  */ 
@Configuration 
public class QueueConfig { 
    
    @Bean(name = "confirmTestQueue") 
    public Queue confirmTestQueue() { 
        return new Queue("confirm_test_queue", true, false, false); 
    }

    @Bean(name = "confirmTestExchange")
    public FanoutExchange confirmTestExchange() { 
      return new FanoutExchange("confirmTestExchange"); 
    }

    @Bean 
    public Binding confirmTestFanoutExchangeAndQueue( 
      @Qualifier("confirmTestExchange") FanoutExchange confirmTestExchange,         
      @Qualifier("confirmTestQueue") Queue confirmTestQueue) { 
          return BindingBuilder.bind(confirmTestQueue).to(confirmTestExchange); 
    } 
}

生产者

@RunWith(SpringRunner.class) 
@SpringBootTest(classes = RabbitmqApplication.class) 
public class Producer { 
  //注入rabbitmq对象 
  @Autowired private RabbitTemplate rabbitTemplate; 
  //注入 ConfirmCallback对象
  @Autowired private ConfirmCallbackService confirmCallbackService;  

  @Test public void test() { 
    //
    rabbitTemplate.setConfirmCallback(confirmCallbackService); 
    //发送消息 
    rabbitTemplate.convertAndSend("confirmTestExchange1", "", "hello,ConfirmCallback你好"); 
  } 
}

正确情况,ack返回true,表示投递成功。

改变交换机名字,发送到一个不存在的交换机

//发送消息 
rabbitTemplate.convertAndSend("confirmTestExchange1", "", "hello,ConfirmCallback你好");

消息未投递到queue的退回模式

消息从 exchange–>queue 投递失败则会返回一个 returnCallback

生产端通过实现ReturnCallback接口,启动消息失败返回,消息路由不到队列时会触发该回调接口

1、改yml

spring: 
  # rabbitmq 连接配置 
  rabbitmq: publisher-returns: true # 开启退回模式

2、设置投递失败的模式

如果消息没有路由到Queue,则丢弃消息(默认)

如果消息没有路由到Queue,返回给消息发送方ReturnCallBack(开启后)

rabbitTemplate.setMandatory(true);

3、实现returnedMessage方法

启动消息失败返回,消息路由不到队列时会触发该回调接口

package com.rabbitmq.config; 
import org.springframework.amqp.core.Message; 
import org.springframework.amqp.rabbit.core.RabbitTemplate; 
import org.springframework.stereotype.Component; 

@Component 
public class ReturnCallbackService implements RabbitTemplate.ReturnCallback { 

  /** @param message 消息对象 
    * @param replyCode 错误码 
    * @param replyText 错误信息 
    * @param exchange 交换机 
    * @param routingKey 路由键 
    */
    @Override 
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { 

      System.out.println("消息对象===>:" + message); 
      System.out.println("错误码===>:" + replyCode); 
      System.out.println("错误信息===>:" + replyText); 
      System.out.println("消息使用的交换器===>:" + exchange); 
      System.out.println("消息使用的路由key===>:" + routingKey); 
      //TODO ===>做业务处理 
    } 
}

4、测试

生产者

@RunWith(SpringRunner.class) 
@SpringBootTest(classes = RabbitmqApplication.class) 
public class Producer { 

    @Autowired 
    private RabbitTemplate rabbitTemplate;
 
    //注入rabbitmq对象 
    @Autowired 
    private ConfirmCallbackService confirmCallbackService; 

    @Autowired 
    private ReturnCallbackService returnCallbackService; 

    @Test 
    public void test() { 

      /** 
       * 确保消息发送失败后可以重新返回到队列中 
       */ 
      rabbitTemplate.setMandatory(true); 

      /**
       * 消息投递到队列失败回调处理 
       */     
      rabbitTemplate.setReturnCallback(returnCallbackService); 

      /**
       * 消息投递确认模式 
       */           
      rabbitTemplate.setConfirmCallback(confirmCallbackService); 

      //发送消息 
      rabbitTemplate.convertAndSend("confirmTestExchange", "info", "hello,ConfirmCallback你好"); 
    } 
}

如果不存在路由key,会调用ReturnCallback接口

消费端

消息确认机制ack

ack指Acknowledge确认。 表示消费端收到消息后的确认方式

消费端消息的确认分为:自动确认(默认)、手动确认、不确认

  • AcknowledgeMode.NONE:不确认

  • AcknowledgeMode.AUTO:自动确认

  • AcknowledgeMode.MANUAL:手动确认

其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从RabbitMQ 的消息 缓存中移除。

但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

1、yml

spring: 
  rabbitmq: 
    listener: 
      simple: 
        acknowledge-mode: manual # 手动确认

2、确认配置

@Component 
@RabbitListener(queues = "confirm_test_queue") 
public class ReceiverMessage { 

  @RabbitHandler 
  public void processHandler(String msg, Channel channel, Message message) throws IOException { 
  
    long deliveryTag = message.getMessageProperties().getDeliveryTag(); 
    try {

      System.out.println("消息内容===>" + new String(message.getBody())); 

      //TODO 具体业务逻辑 


      //手动签收[参数1:消息投递序号,参数2:批量签收] 
      channel.basicAck(deliveryTag, true); 
    } catch (Exception e) { 
      //拒绝签收[参数1:消息投递序号,参数2:批量拒绝,参数3:是否重新加入队列] 
      channel.basicNack(deliveryTag, true, true); 
    } 
  } 
}

channel.basicNack 方法与 channel.basicReject 方法区别在于basicNack可以批量拒绝多条消息,而basicReject一次只能拒绝一条消息。

posted @ 2022-07-07 00:12  snail灬  阅读(246)  评论(0编辑  收藏  举报