# Spring Boot学习 RabbitMQ 消息队列 (二) SpringBoot整合 设置消息确认机制,可靠抵达 ConfirmCallbac ReturnCallback

Spring Boot学习 RabbitMQ 消息队列 (二) SpringBoot整合 设置消息确认机制,可靠抵达 ConfirmCallbac ReturnCallback

Spring Boot学习 RabbitMQ 消息队列 (一) 消息队列 基本概念 web端操作

3. SpringBoot整合 RabbitMQ

3.1 环境搭建

3.1.1 导入整合依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

3.1.2 配置文件

spring.rabbitmq.host=192.168.231.1
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

3.1.3 @EnableRabbit

@EnableRabbit  //开启基于注解的RabbitMQ模式
@SpringBootApplication
public class AmqpApplication {
    
    
   public static void main(String[] args) {
    
    
      SpringApplication.run(AmqpApplication.class, args);
   }
}

3.1.4 SpringBoot对rabbitmq的自动配置

/**
 * 引入 spring-boot-starter-amqp 场景启动器后,自动配置生效
 * 自动配置
 *  1、RabbitAutoConfiguration
 *  2、自动配置了连接工厂ConnectionFactory;
 *  3、RabbitProperties 封装了 RabbitMQ的配置
 *  4、 RabbitTemplate :给RabbitMQ发送和接受消息;
 *  5、 AmqpAdmin : RabbitMQ系统管理功能组件;
 *     AmqpAdmin:创建和删除 Queue,Exchange,Binding
 *  6、@EnableRabbit +  @RabbitListener 监听消息队列的内容
 *
 */

3.2 AmqpAdmin系统管理功能组件

作用:操作exchange、queue、bind

   @Autowired
   AmqpAdmin amqpAdmin;
   @Test
   public void createExchange(){
    
    

 	//创建不同类型的交换机使用不同的类作为参数
    //DirectExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments)
    amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
    System.out.println("创建完成");
    //创建队列   
    amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));
      //创建绑定规则
/**
 * new Binding("amqpadmin.queue", 
 	Binding.DestinationType.QUEUE,
 	"amqpadmin.exchange",
 	"amqp.haha",
 	null)
 * 	目的地(队列名or交换器名)、
 	目的地类型(绑定到队列or交换器)、
 	绑定的交换器名称、
 	路由键、
 	MAP的参数集合
 */
    amqpAdmin.declareBinding(new Binding("amqpadmin.queue", 		 Binding.DestinationType.QUEUE,"amqpadmin.exchange","amqp.haha",null));

   }

3.3 rabbitTemplata对象的使用

作用:给RabbitMQ发送和接受消息

@SpringBootTest
class AmqpApplicationTests {
    
    
   @Autowired
   RabbitTemplate rabbitTemplate;
   @Autowired
   AmqpAdmin amqpAdmin;
   /**
    * 1、单播(点对点)
    */
   @Test
   public void contextLoads() {
    
    
      //方法一:send   Message需要自己构造一个;定义消息体内容和消息头
      //rabbitTemplate.send(exchage,routeKey,message);

      //方法二:convertAndSend   object默认当成消息体,只需要传入要发送的对象,自动序列化发送给rabbitmq;
      //使用这种方法,发送的对象必须实现序列化接口 Serializable
      //rabbitTemplate.convertAndSend(exchage,routeKey,object);
      Map<String,Object> map = new HashMap<>();
      map.put("msg","这是第一个消息");
      map.put("data", Arrays.asList("helloworld",123,true));
      //对象被默认序列化以后发送出去
      /**
       * 传入一个交换器和一个路由键
       */
      rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",map);

   }
   /**
    * 广播
    */
   @Test
   public void sendMsg(){
    
    
       //广播无需指定路由键且需要发送到fanout类型的交换器
      rabbitTemplate.convertAndSend("exchange.fanout","",new Book("红楼梦","曹雪芹"));

   } 
    
   //接受数据
   @Test
   public void receive(){
    
    
      Object o = rabbitTemplate.receiveAndConvert("atguigu.news");
      System.out.println(o.getClass());
      System.out.println(o);
   }
}

3.4 配置json序列化方式

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyRabbitmqConfig {
    
    
    @Bean
    public MessageConverter jsonMessageConverter(){
    
    
        return new Jackson2JsonMessageConverter();
    }
}

3.5 Rabbit监听注解

3.5.1 @RabbitListener

  • Queue可以很多被很多方法同时监听.只要收到消息,队列信息删除,而且只能有一个收到此消息
  • 同一个消息,只有一个客户端可以收到
  • 只有一个消息完全处理完,方法运行结束后,才会去接受下一个消息
/**参数可以写一下三个类型:
    * 1. Message message :原生消息的详细信息  头+体
    * 2. T<发送的对象类型>:
    * 3. Channel :当前传输数据的信道,用于手动ack
    */
@Service
public class BookService {
    
    
    //queues 表示监听的队列
    @RabbitListener(queues = "book.news")
    public void receive(Book book){
    
    
        System.out.println("收到消息"+book);
    }

    @RabbitListener(queues = "book.news")
    public void receive02(Message message){
    
    
        System.out.println("收到消息"+message.getBody());
        System.out.println("收到消息"+message.getMessageProperties());
    }
}

3.5.1 @RabbitHandler

  • RabbitHandler 只能标注在方法上,能够根据参数object的类型进行获取,重载区分同队列中的不同消息
  • RabbitListener 可以标注在类和方法上

使用场景:当一个队列(a.q)中有同时2种消息(对象)时,可以同时使用RabbitListener 加RabbitHandler 进行区分

@RabbitListener(queues ="a.q")
@Service
public class BookService {
    
    
    @RabbitHandler 
    public void receive(Book book){
    
    
        System.out.println("收到消息"+book);
    }
    @RabbitHandler 
    public void receive02(Student student){
    
    
        System.out.println("收到消息"+student);
    }
}

此时,receive02方法只会处理消息内容为Student的消息,receive方法只会处理消息内容为Book的消息.

3.6 设置消息确认机制,可靠抵达

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

定制RabbitTemplata 实现消息确认机制

  • 服务端broker收到消息的回调
    • spring.rabbitmq.publisher-confirm-type=correlated
    • 设置确认回调 setConfirmCallback
  • 消息正确抵达队列进行回调
    • spring.rabbitmq.publisher-returns=true
    • spring.rabbitmq.template.mandatory=true
    • 设置确认回调setReturnCallback
  • 消费端确认(保证每个消息被正确消费,此时才可以让broker删除这个信息)
    • 默认是自动确认(即方法接受到消息后直接确认,无论方法执行是否出现问题)
    • 因此需要开启手动ack模式.只要我们没有明确告诉mq(在监听方法中使用Channel参数),就不会删除队列中的消息
    • spring.rabbitmq.listener.simple.acknowledge-mode=manual
    • 在监听方法中使用方法进行确认:
      • channel.basicAck(deliveryTag,false); 签收,第二个参数表示是否批量确认
      • channel.basicNack(deliveryTag,false,true)拒签,第二个参数表示是否批量确认,第三个参数表示是否让该消息重新回队列

配置文件:

spring.rabbitmq.host=192.168.231.1
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#设置服务器收到消息确认回调  还需要再配置类中定制rabbitTemplate
#spring.rabbitmq.publisher-confirms 过时
spring.rabbitmq.publisher-confirm-type=correlated
#设置服务端正确将消息传到队列的确认回调 还需要再配置类中定制rabbitTemplate
spring.rabbitmq.publisher-returns=true
#只要抵达队列,以异步发送优先回调spring.rabbitmq.publisher-returns
spring.rabbitmq.template.mandatory=true
#开启消费端手动ack,再业务中进行确认   利用方法参数中的通道Channel
spring.rabbitmq.listener.simple.acknowledge-mode=manual

配置类:

@Configuration
public class MyRabbitmqConfig {
    
    

    @Resource
    RabbitTemplate rabbitTemplate;

    //设置序列化类型
    @Bean
    public MessageConverter jsonMessageConverter(){
    
    
        return new Jackson2JsonMessageConverter();
    }

    /**
     * 定制rabbitTemplate  实现可靠抵达
     */
    //@PostConstruct  表示当构造器MyRabbitmqConfig构造完成后 再调用此方法
    @PostConstruct
    public void initRabbitTemplate(){
    
    
        //设置服务器收到消息确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    
    
            /**
             *
             * @param correlationData   当前消息的唯一关键数据(id)  发送消息时可以指定UUID
             * @param ack   消息是否成功收到
             * @param cause 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    
    
                //当消息成功抵达消息中间件后会调用
                //处理具体的业务
                System.out.println(correlationData.toString()+ack+cause);
            }
        });

        //设置消息正确抵达队列的回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
    
    
            /**
             * 只要消息没有投递给指定的队列,就触发这个失败回调
             * @param message  消息的详细内容
             * @param replyCode 回复的状态码
             * @param replyText 回复的文本内容
             * @param change    当时这个消息发给哪个交换机
             * @param routingKey    当时这个消息使用哪个路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String change, String routingKey) {
    
    
                //只要消息没有投递给指定的队列,就触发这个失败回调
                //处理具体的业务
                System.out.println(message.toString()+replyCode+replyText+change+routingKey);
            }
        });
    }
}

手动ack案例:

@RabbitListener(queues ="a.q")
@Service
public class BookService {
    
    
    @RabbitHandler 
    public void receive02(Message message,Student student,Channel channel){
    
    
        System.out.println("收到消息"+student);
        //发送ack确认
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        channel.basicAck(deliveryTag,false);
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44634197/article/details/108581451