1.由RabbitMQ发送与消费消息的模型可知消息丢失发生在以下几种情况。
1、生产者发送消息未到达交换机
2、消息到达交换机,没有正确路由到队列
3、MQ宕机,队列中的消息不见了
4、消费者收到消息,还没消费,消费者宕机
2.如何保证消息不丢失?
生产者确认机制
(1)配置文件
spring:
rabbitmq:
publisher-confirm-type: correlated
publisher-returns: true
template:
mandatory: true
(2)定义ConfirmCallback
ConfirmCallback可以在发送消息时指定,因为每个业务处理confirm成功或失败的逻辑不一定相同。
@Test
public void confirmAndReturnTest() throws Exception {
String uuid = UUID.randomUUID().toString().replace("-", "");
CorrelationData correlationData = new CorrelationData(uuid);
correlationData.getFuture().addCallback(result -> {
//没出异常,走这里
if(result.isAck()) {
log.warn("666---> 消息正确到达交换机:" + correlationData.getId());
} else {
log.error("发送失败了,没有到达交换机:"+ correlationData.getId());
//记录日志,重发
log.info("发送失败,重新发送消息--->222");
//消息发送失败回调的方法,一个消息对应一个
}
}, ex -> {
//出异常,走这里
log.error("发送消息出问题了:" + ex.getMessage() + " --- " + correlationData.getId());
//记录日志,重发
log.info("出异常,重新发送消息--->111");
});
(3)定义Return回调
每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目加载时配置。
@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RabbitTemplate bean = applicationContext.getBean(RabbitTemplate.class);
bean.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
//投递失败,记录口志
log.error("消息发送失败,应答码:{},失败文本原因:{},交换机:{} ,路由键: {},消息:{}",
replyCode, replyText, exchange, routingKey, message.toString());
log.warn("重发消息");
//rabbitTemplate.convertAndSend(exchange, routingKey, message.getBody());
});
}
}
持久化机制(交换机、队列、消息进行持久化)
消费者ack机制
(1)none:只要消息到达消费者,Spring直接返回ack给MQ
(2)消费者开启手动ack,或者自动ack + 重试耗尽的失败策略,定义错误交换机队列,后期通过人工进行干预,设置了重试次数。如果一直失败的话,肯定有问题了。记录日志,人工干预。
(3)auto:自动ack。消费消息不出异常,返回ack给MQ。消费消息出异常了,返回nack,把消息重回队列。
配置如下:
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true #开启消费者失败重试
initial-interval: 1000 #初始的失败等待时长为1秒
multiplier: 2 #失败的等待时长倍数,下次等待时长 = multiplier * last-interval
max-attempts: 3 #最大重试次数
stateless: true #true无状态;false有状态。如果业务中包含事务,这里改为false
(1) 消费者的操作是否是一个幂等性操作? 什么是幂等性?
多次执行同一个操作,最终的结果是一样的。
(2)对于非幂等性操作,多次消费消息,会造成数据一致性的问题,所以要保证重复消费消息的问题,如何解决?
生产者生产消息的速度 远高于 消费者消费消息的速度?于是就会造成消息积压在MQ。
假设是消费者引起的,解决消费者代码或者临时开启多个消费者,来以倍速消费积压的消息。当积压的消息消费的差不多的情况,关闭临时消费者。
四、死信交换机
在RabbitMQ中,没有延迟队列的功能。可以使用 TTL + 死信队列 的方式实现延迟队列。应用场景(延迟发送短信,付款时间等等)
原理图如下:
通过DelayExchange插件实现延迟队列
生产者发送消息: