消息回调

廉价感情. 提交于 2020-01-12 05:30:09

0. 项目结构
rabbitmq04
rabbitmq-provider
rabbitmq-consumer
common
在这里插入图片描述

1. 什么是消息回调
消息回调,其实就是消息确认(生产者推送消息成功,消费者接收消息成功)

2. 为什么要进行消息确认
经常会听到丢消息的字眼, 对于程序来说,发送者没法确认是否发送成功,消费者处理失败也无法反馈,
没有消息确认机制,就会出现消息莫名其妙的没了,也不知道什么情况

3. 生产者推送消息[确认]
0.前提:使用直连交换机完成消息的发送和接收

1.在rabbitmq-provider项目的application.yml文件上,添加消息确认的配置项
#1.开启 confirm 确认机制
spring.rabbitmq.publisher-confirms=true
#2.开启 return 确认机制
spring.rabbitmq.publisher-returns=true
#3.设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
spring.rabbitmq.template.mandatory=true

server:
  port: 8081
  servlet:
    context-path: /rabbitmq-provider
spring:
  rabbitmq:
    virtual-host: /
    username: guest
    password: guest
    host: 192.168.186.136
    port: 5672
    #1.开启 confirm 确认机制
    publisher-confirms: true
    #2.开启 return 确认机制
    publisher-returns: true
    #3.设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
    template:
      mandatory: true

 注1:设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
      spring.rabbitmq.template.mandatory=true//此行配置与正面代码效果一样 
      rabbitTemplate.setMandatory(true);//此行代码也配置项3的效果一致

2.创建RabbitTemplateConfig,在里面创建自定义RabbitTemplate并添加2个相关的回调函数
RabbitTemplate.ConfirmCallback
通过实现ConfirmCallback 接口,消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是只确认是否正确到达 Exchange 中
还需要在配置文件添加配置spring.rabbitmq.publisher-returns=true

package com.lyl.rabbitmqprovider.rabbitmq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @authorlyl
 * @site
 * @company
 * @create  2019-12-27 11:16
 */
@Configuration
@Slf4j
public class RabbitTemplateConfig {
    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory, Jackson2JsonMessageConverter jackson2JsonMessageConverter) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);//指定json转换器
        return rabbitTemplate;
    }

    @Bean
    public Jackson2JsonMessageConverter jackson2JsonMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }


    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        rabbitTemplate.setMandatory(true);
        //生产者推送消息的回调机制
        //流程:生产者->交换机->路由键->队列

        //消息回调确认:生产者(Provider)->交换机(Exchange)
        //2种情况:
        //1)如果消息没有到exchange,则confirm回调,ack=false
        //2)如果消息到达exchange,则confirm回调,ack=true
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                log.info("ConfirmCallback,相关数据:{}", correlationData);
                log.info("ConfirmCallback,确认消息:{}", ack);
                log.info("ConfirmCallback,原因:{}", cause);
            }
        });

        //消息回调确认:交换机(Exchange)->队列(Queue)
        //2种情况:
        //1)exchange到queue成功,则不回调return
        //2)exchange到queue失败,则回调return(需设置mandatory=true,否则不执行回调函数,消息就丢了)
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                log.info("ReturnCallback,消息:{}", message);
                log.info("ReturnCallback,回应码:{}", replyCode);
                log.info("ReturnCallback,回应信息:{}", replyText);
                log.info("ReturnCallback,路由器:{}", exchange);
                log.info("ReturnCallback,路由键:{}", routingKey);
            }
        });
        return rabbitTemplate;
    }

}
 RabbitTemplate.ReturnCallback
   通过实现 ReturnCallback 接口,启动消息失败返回,比如路由不到队列时触发回调
   还需要在配置文件添加配置spring.rabbitmq.publisher-returns=true

3.ConfirmCallback和ReturnCallback这2个回调函数在什么情况会触发
先从总体的情况分析,推送消息存在四种情况
A.消息推送到server,找不到交换机
B.消息推送到server,找到交换机了,但是没找到队列
C.消息推送到server,交换机和队列啥都没找到(C和A效果是一样,交换机都没找到,更不用说队列了)
D.消息推送成功

4.小结:

 流程:由生产者 -------> 交换机(Exchange)
 ConfirmCallBack:
 如果消息没有到exchange,则confirm回调,ack=false
 如果消息到达exchange,则confirm回调,ack=true

 流程:由交换机(Exchange)--------> 队列(Queue)
 ReturnCallBack:
 exchange到queue成功,则不回调return
 exchange到queue失败,则回调return(需设置mandatory=true,否则不执行回调函数,消息就丢了) 

5.注意:spring-rabbit和原生的rabbit-client ,表现是不一样的.测试的时候,原生的client,exchange错误的话,直接就报错了,是不会到confirmListener和returnListener的

4. 消费者接收消息[确认]
和生产者的消息确认机制不同,因为消息接收本来就是在监听消息,符合条件的消息就会消费下来。
所以,消息接收的确认机制主要存在三种模式
1.自动确认
这也是默认的消息确认情况。AcknowledgeMode.NONE,RabbitMQ成功将消息发出(即将消息成功写入TCP Socket)中立即
认为本次投递已经被正确处理,不管消费者端是否成功处理本次投递

 当自动应答等于true的时候,表示当消费者一收到消息就表示消费者收到了消息,消费者收到了消息就会立即从队列中删除

2.不确认(不介绍)

3.手动确认(多数选择的模式)
消费者收到消息后,手动调用basic.ack/basic.nack/basic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功
basic.ack用于肯定确认
basic.nack用于否定确认
basic.reject用于否定确认,但与basic.nack相比有一个限制:一次只能拒绝单条消息

 消费者端以上的3个方法都表示[消息已经被正确投递],但是basic.ack表示消息已经被正确处理,
 但是basic.nack,basic.reject表示没有被正确处理,但是RabbitMQ中仍然需要删除这条消息 

消息接收手动确认编码开始

4.修改FirstQueueReceiver代码,继承AbstractAdaptableMessageListener并重写onMessage(Message message, Channel channel)方法
1.消费者消息确认相关类和接口的体系结构

package com.lyl.rabbitmqconsumer.rabbitmq;

import com.lyl.commonvo.vo.OrderVo;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * @authorlyl
 * @site
 * @company
 * @create  2019-12-29 23:00
 */
@Component
@Slf4j
@RabbitListener(queues = {"rollack-queue"})
public class QueueRecevier extends AbstractAdaptableMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws IOException {
        //消息唯一标识ID,单调正整数,当multiple=true,一次性处理小于或等于deliveryTag的消息
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        boolean multiple = false;//是否批量处理,true:批量   false:单条

//        channel.basicAck(deliveryTag, multiple);
//        channel.basicReject(deliveryTag,true);//为true会重新放回队列
//        channel.basicNack(deliveryTag, multiple,true)

        try {
            String msg = new String(message.getBody(),"UTF-8");
            log.info("msg={}",msg);

            for (int i =0;i < 4 ; i++){
                Thread.sleep(1000);
                System.out.println("...");
            }

            log.info("deliveryTag={}",deliveryTag);
            if (deliveryTag%2==0)
                throw new RuntimeException("偶数是错的!!");
            log.info("消息已被正确确认");

            channel.basicAck(deliveryTag, multiple);
        } catch (Exception e) {
            e.printStackTrace();
            channel.basicReject(deliveryTag,true);//为true会重新回到队列
        }


    }
}
   注1: basicAck(long deliveryTag, boolean multiple)方法的两个参数
         deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用
           basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 
           投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
         multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 
           小于等于传入值的所有消息

5.新建MessageListenerConfig,添加消息接收确认机制相关代码

package com.lyl.rabbitmqconsumer.rabbitmq;

import com.lyl.rabbitmqconsumer.controller.QueueRecevier;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @authorlyl
* @site
* @company
* @create  2019-12-29 23:15
*/
@Configuration
public class MessageListenerConfig {

   @Bean
   public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory, Jackson2JsonMessageConverter jackson2JsonMessageConverter) {
       SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
       factory.setConnectionFactory(connectionFactory);
       factory.setMessageConverter(jackson2JsonMessageConverter);
       return factory;
   }

   @Bean
   public Jackson2JsonMessageConverter jackson2JsonMessageConverter(){
       return new Jackson2JsonMessageConverter();
   }

   @Bean
   public SimpleMessageListenerContainer simpleMessageListenerContainer(
           ConnectionFactory connectionFactory,
           RabbitQueueConfig rabbitQueueConfig,
           QueueRecevier queueRecevier){
       SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
       container.setConnectionFactory(connectionFactory);//设置连接工厂

       container.setConcurrentConsumers(1);//设置并发消费者
       container.setMaxConcurrentConsumers(1);//设置最多的并发消费者
       container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息

       //注意:此处不能使用Autowired根据类型自动注入队列,必须调用rabbitmqDirectConfig.firstQueue()获得,why?
       // 因为项目中可能存在多个队列,它们的类型都是Queue,自动注入会报错
       container.setQueues(rabbitQueueConfig.queue());//设置监听队列
       container.setMessageListener(queueRecevier);//设置消息监听器
       return container;
   }
}
 //注意:此处不能使用Autowired根据类型自动注入队列,必须调用rabbitmqDirectConfig.firstQueue()获得,why?
 // 因为项目中可能存在多个队列,它们的类型都是Queue,自动注入会报错
 container.setQueues(rabbitmqDirectConfig.firstQueue());
 container.setMessageListener(directReceiver);
1:SimpleMessageListenerContainer即简单消息监听容器
          这个类非常的强大,我们可以对他进行很多的设置,用对于消费者的配置项,这个类都可以满足
          1.它可以设置事务特性、事务管理器、事务属性、事务并发、是否开启事务、回滚消息等。但是我们在实际生产中,很少使用事务,基本都是采用补偿机制
          2.它可以设置消费者数量、最小最大数量、批量消费
          3.它可以设置消息确认和自动确认模式、是否重回队列、异常捕获 Handler 函数
          4.它可以设置消费者标签生成策略、是否独占模式、消费者属性等
          5.它还可以设置具体的监听器、消息转换器等等
          另外,SimpleMessageListenerContainer 还可以进行动态设置,比如在运行中的应用可以动态的修改
          其消费者数量的大小、接收消息的模式等。
     注2:消息确认模式
          AcknowledgeMode.NONE:自动确认
          AcknowledgeMode.AUTO:根据情况确认
          AcknowledgeMode.MANUAL:手动确认  

5. Rabbitmq的消息确认的两种方式
Rabbitmq为了确认并且保证消息被送达,提供了两种方式:发布确认和事务。(两者不可同时使用)
在channel为事务时,不可引入确认模式;同样channel为确认模式下,不可使用事务。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!