消息确认消费机制
- 消息高可用和确认消费
- 常见消息确认模式介绍
- 基于自动模式消费确认
- 基于手动模式确认
消息高可用和确认消费
RabbitMQ在实际使用过程中,如果配置或使用不当,则会出现令人头疼的问题,下面列举三个
- 不知道发送的消息是否成功,生产者将消息发送出去,但是如果消息模型不存在,队列不存在,就是发送失败的,这一点作为生产者要知道
解决方案
RabbitMQ会要求生产者在发送完信息后,进行发送确认,当成功就代表消息成功的发送出去,在配置文件中
@Bean
public RabbitTemplate rabbitTemplate(){
// 设置“发送消息后返回确认信息”
connectionFactory.setPublisherReturns(true);
// 构造发送消息组件实例对象
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 这里是为了消息确认和手动ACK
rabbitTemplate.setMandatory(true);
// 设置消息在传输中的格式,在这里采用JSON的格式进行传输
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
// 发送消息后,如果发送成功,则输出“消息发送成功”的反馈信息
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("消息发送成功:correlationData({}),ack({}), cause({})", correlationData,ack,cause);
}
});
// 发送消息后,如果发送失败,则输出“消息丢失”的反馈信息
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("消息丢失:exchange({}),route({}),replyCode ({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,
message);
}
});
// 最终返回RabbitMQ的操作组件实例RabbitTemplate
return rabbitTemplate;
}
上述代码就实现了发送成功的回调
- 如果出现特殊原因,RabbitMQ的服务宕机了,需要重启,此时队列中有大量未消耗的消息,就会造成重启过程中,消息丢失
解决方案
RabbitMQ是建议将交换机、队列持久化设置durable为true,并且也要将消息的持久化模式也为true
@RequestMapping("/sendReply")
public void send(){
CorrelationData messageId = new CorrelationData(UUID.randomUUID().toString());
// 1指定exchange
Message message = MessageBuilder
.withBody("asddas".getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_BYTES)
.setContentEncoding("ute-8")
.setMessageId(UUID.randomUUID().toString())
.setDeliveryMode(MessageDeliveryMode.PERSISTENT)
.build();
rabbitTemplate.convertAndSend("boot-topic-exchange","fast.red.dog",message);
}
// 创建队列
@Bean(name = "basicQueue")
public Queue basicQueue() {
/*
// durable :是否持久化(宕机以后重新发布)
// exclusive : 是否排外,当前队列只能被一个消费者消费
// autoDelete 如果这个队列没有消费者,队列是否被删除
// arguments 指定当前队列的其他信息
*/
return new Queue("basicQueue",true,false,false,null);
}
- 消费幂等性的问题,消费者再监听消息的时候,如果监听失败或者生产者宕机,导致队列找不到消费者而不断的重新入队列,最终出现消息被重复消费的现象。
解决方案
RabbitMQ提供了ACK模式,即消息确认模式,有三种,NONE\AUTO\MANUAL,不同的确认机制,底层的执行逻辑和应用场景不一样的。在实际的环境中,都是通过消息确认后才删除消息,
常见消息确认模式介绍
NONE:无须确认模式,消费者消费消息后,不需要反馈任何消息给生产者
AUTO:自动确认模式,生产者监听到一个消息时,注意是监听,不是完成消费,发送一个响应信号ACK给RabbitMQ服务器,之后将消息从RabbitMQ移除,这是RabbitMQ的默认行为
MANUAL:手动确认消费模式,这是一种以代码的形式发送一个ACK反馈信息给RabbitMQ服务器,之后该消息在RabbitMQ的队列中移除。
由于NONE模式不严谨,实际使用比较少见,所以建议尽量少用。
基于自动模式消费确认
配置
#全局设置队列的确认消费模式;如果队列对应的消费者没有指定消费确认模式,则将默认 指定全局 #配置的auto模式
spring.rabbitmq.listener.simple.acknowledge-mode=auto
或者修改配置工厂
/**
* 下面为单一消费者实例的配置
* @return
*/
@Bean(name = "singleListenerContainer")
public SimpleRabbitListenerContainerFactory listenerContainer(){
// 定义消息监听器所在的容器工厂
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
// 设置容器工厂所用的实例
factory.setConnectionFactory(connectionFactory);
// 设置消息在传输中的格式,在这里采用JSON的格式进行传输
factory.setMessageConverter(new Jackson2JsonMessageConverter());
// 设置并发消费者实例的初始数量,在这里为1个
factory.setConcurrentConsumers(1);
// 设置并发消费者实例的最大数量,在这里为1个
factory.setMaxConcurrentConsumers(1);
// 设置并发消费者实例中每个实例拉取的消息数量,在这里为1个
factory.setPrefetchCount(1);
//设置确认消费模式为自动确认消费(这里可以在配置文件配置
// spring.rabbitmq.listener.simple.acknowledge-mode=auto)
factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
return factory;
}
/**创建自动消息确认-----------------------------------------------**/
//创建队列
@Bean(name = "autoQueue")
public Queue autoQueue(){
//创建并返回队列实例
return new Queue("autoQueue",true);
}
//创建交换机
@Bean
public DirectExchange autoExchange(){
//创建并返回交换机实例
return new DirectExchange("autoExchange",true,false);
}
//创建绑定
@Bean
public Binding autoBinding(){
//创建并返回队列交换机和路由的绑定实例
return BindingBuilder.bind(autoQueue()).to(autoExchange()).with("auto-key");
}
@RequestMapping("/testAutoConfirm")
public ResultVo testAutoConfirm(@RequestBody KnowledgeInfo model) {
rabbitTemplate.convertAndSend("autoExchange", "auto-key", model, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 获取消息的属性
MessageProperties messageProperties = message.getMessageProperties();
// 设置消息的持久化模式
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
// 设置消息的类型(在这里指定消息类型为User类型)
messageProperties.setHeader(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME,KnowledgeInfo.class);
//返回消息实例
return message;
}
});
log.info("auto机制生产者发送对象消息{}",model);
return ResultVo.success("auto机制生产者发送对象消息成功");
}
/** AUTO机制确认模式
* @param model
*/
@RabbitListener(queues = "autoQueue",containerFactory = "singleListenerContainer")
public void receiveAutoQueue(@Payload KnowledgeInfo model){
try {
//输出日志
log.info("基于AUTO的确认消费模式-消费者监听消费消息-内容为: {} ",model);
}catch (Exception e){
log.error("基于AUTO的确认消费模式-消费者监听消费消息-发生异常:",e.fillInStackTrace()); }
}
基于手动模式确认
这回使用配置文件配置手动确认
#全局设置队列的确认消费模式;如果队列对应的消费者没有指定消费确认模式,则将默认 指定全局 #配置的auto模式
spring.rabbitmq.listener.simple.acknowledge-mode=manual
/**创建手动消息确认-----------------------------------------------**/
//创建队列
@Bean(name = "manualQueue")
public Queue manualQueue(){
//创建并返回队列实例
return new Queue("manualQueue",true);
}
//创建交换机
@Bean
public DirectExchange manualExchange(){
//创建并返回交换机实例
return new DirectExchange("manualExchange",true,false);
}
//创建绑定
@Bean
public Binding manualBinding(){
//创建并返回队列交换机和路由的绑定实例
return BindingBuilder.bind(manualQueue()).to(manualExchange()).with("manual-key");
}
/** 手动机制确认模式
* @param model
*/
@RabbitListener(queues = "manualQueue")
public void receiveAutoQueue(@Payload KnowledgeInfo model, Channel channel, Message message) throws IOException {
// 获取消息分发时的全局唯一标识
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//输出日志
log.info("基于手动的确认消费模式-消费者监听消费消息-内容为: {} ",model);
// 获取消息分发时的全局唯一标识
deliveryTag = message.getMessageProperties().getDeliveryTag();
// 测试异常
int i = 1/0;
// 执行完业务逻辑后,手动进行确认消费,其中第1个参数:消息的分发标识( 全局唯一);第2个参数:是否允许批量确认消费(在这里设置为true)
channel.basicAck(deliveryTag,true);
}catch (Exception e){
//如果在处理消息的过程中发生了异常,则照样需要人为手动确认消费该消息,
//该消息将一直留在队列中,从而将导致消息的重复消费,如果第二个参数为false则抛弃
channel.basicReject(deliveryTag,true);
//否则该消息将一直留在队列中,从而将导致消息的重复消费
log.error("基于手动的确认消费模式-消费者监听消费消息-发生异常:",e);
}
}
@RequestMapping("/testManualConfirm")
public ResultVo testManualConfirm(@RequestBody KnowledgeInfo model) {
rabbitTemplate.convertAndSend("manualExchange", "manual-key", model, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 获取消息的属性
MessageProperties messageProperties = message.getMessageProperties();
// 设置消息的持久化模式
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
// 设置消息的类型(在这里指定消息类型为User类型)
messageProperties.setHeader(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME,KnowledgeInfo.class);
//返回消息实例
return message;
}
});
log.info("手动确认机制生产者发送对象消息{}",model);
return ResultVo.success("手动确认机制生产者发送对象消息成功");
}
如果我们重启项目,会再次进入监听,直到异常去掉,消息被手动确认