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为确认模式下,不可使用事务。
来源:CSDN
作者:傻_
链接:https://blog.csdn.net/lei_lin/article/details/103773037