消息可靠性
1、消息丢失的可能性:
• 生产者发送消息时丢失: -- 生产者发送信息室连接 MQ失败 -- 生产者发送信息达到MQ后未找到交换机 -- 生产者发送消息到达MQ的交换机后,未找到合适的Queue -- 消息到达MQ后,处理消息的进程发生异常
• MQ导致消息丢失 -- 消息到达MQ,保存到队列后,尚未消费就突然宕机;
• 消费者处理消息时: -- 消息接受后尚未处理突然宕机 -- 消息接收后处理过程抛出异常
2、保证MQ的可靠性
- 保证生产消息的可靠性
- 确保MQ不会将消息弄丢
- 保证消费消息的可靠性
生产消息可靠性
生产者确认机制
1、publisher-confirm 消息成功投递到交换机,返回ack;消息未成功投递到交换机,返回nack(记录消息以及交换机等相关信息到数据库,后期可以编写任务去补偿发送,如定时任务) 2、publisher-return 未正确到达队列(消息成功投递单路由失败),返回ack及失败原因
3、开启生产者确认,在生产者模块中的配置文件中添加如下配置
spring:
rabbitmq:
publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型
publisher-returns: true # 开启publisher return机制
这里publisher-confirm-type中有三种模式可选,
-
none :关闭confirm机制
-
simple:同步阻塞等待MQ的回执(回调方法)
-
correlated: MQ异步回调返回回执;
实现方法:
- 定义ConfirmCallback:ConfirmCallback可以在发送消息时指定,因为每个业务处理confirm成功与否逻辑不确定
package com.itheima.publisher.config;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Slf4j
@AllArgsConstructor
@Configuration
public class MqConfig {
private final RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returned) {
log.error("触发return callback,");
log.debug("exchange: {}", returned.getExchange());
log.debug("routingKey: {}", returned.getRoutingKey());
log.debug("message: {}", returned.getMessage());
log.debug("replyCode: {}", returned.getReplyCode());
log.debug("replyText: {}", returned.getReplyText());
}
});
}
}
- 定义Return回调:每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目加载时配置
发送失败处理机制、
在ConfirmCallback中收到nack表示消息投递失败,ReturnCallback异常表示路由失败:
可以将消息记录到失败消息表,由定时任务进行发布,每隔10秒钟(可设置)执行获取失败消息重新发送,发送一次则在失败次数字段加一,达到3次停止自动发送由人工处理。
消息持久化
为了提升性能,默认情况下MQ的数据都是在内存存储的临时数据,重启后就会消失。为了保证数据的可靠性,必须配置持久化,包括:
- 交换机持久化----默认就是持久化----durable默认就是true
- 队列持久化----默认就是持久化-----durable默认就是true
- 消息持久化-----默认就是持久化------在发送消息时,使用Message对象,并设置delivery-mode为持久化
消费者确认机制
- 介绍
为了确认消费者是否成功处理消息,RabbitMQ提供了消费者确认机制(Consumer Acknowledgement)。即:当消费者处理消息结束后,应该向RabbitMQ发送一个回执,告知RabbitMQ消息处理状态。回执有三种可选值:
- ack:成功处理消息,RabbitMQ从队列中删除该消息
- nack:消息处理失败,RabbitMQ需要再次投递消息
- reject:消息处理失败并拒绝该消息,RabbitMQ从队列中删除该消息
ack取值
rabbitmq:
listener:
simple:
acknowledge-mode: none # 关闭ack
- none 只要消息到达消费者,消费者直接返回ack给MQ,MQ收到ack会把队列中消息删除
- manual(手动ack);
- auto:自动ack。自动ack。消费消息不出异常,返回ack给MQ。消费消息出异常了,返回nack,把消息重回队列
消息重复消费问题
何为幂等性?多次执行同一个操作,最终结果是一样的
在程序开发中,是指同一个业务,执行一次或多次对业务状态的影响是一致的。例如:
- 根据id删除数据
- 查询数据
但数据的更新往往不是幂等的,如果重复执行可能造成不一样的后果。比如:
- 取消订单,恢复库存的业务。如果多次恢复就会出现库存重复增加的情况
- 退款业务。重复退款对商家而言会有经济损失。
所以,我们要尽可能避免业务被重复执行,然而在实际业务场景中,由于意外经常会出现业务被重复执行的情况。例如:
- 页面卡顿时频繁刷新导致表单重复提交
- 服务间调用的重试
- MQ消息的重复投递
解决方案
实例代码
pubic void handleMessage(Message message,Channel channel) throws Exception{
String msg = new String(message.getBody());
log.warn("接受到一个延迟消息{}"+msg);
Long count = redisTemplate.opsForValue().increment(msg);
if(count==1){
//手动ack
channel.basicAck(message.getMesagePropertied.getDeliveryTag(),false);
}
}
死信交换机
概念:如果这个包含死信的队列配置了 dead-letter-exchange 属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为 死信交换机 (Dead Letter Exchange,检查DLX)。
成为死信的几种情况:
-
1.1、消费者使用basic.reject 或 basic.nack 声明消费失败,并且消息的requeue参数设置为false
-
1.2、消息设置了过期时间,或者消息存放的队列设置了过期时间,超时无人消费
-
1.3、要投递的队列消息满了,无法投递
-
这些就是死信,然后会通过路由规则经过交换机路由到一个队列,这个交换机就叫死信交换机,这个队列就是死信队列
2.2、死信的路由过程
-
消费者拒绝消费消息
-
队列绑定了死信交换机
-
死信队列有绑定的死信交换机
TTL
超时未消费,消息变成死信的两种情况:1、消息所在的队列设置了超时时间
2、消息本身设置了超时时间
延迟队列
概念:一种实现消费者延迟收到消息的模式
在RabbitMQ中,没有延迟队列的功能。可以使用 TTL + 死信队列 的方式实现延迟队列
装一个Delay插件,没有队列,不会出现无法消费的问题