聊聊 RabbitMQ 消息可靠性的保证
消息可能会出现的不可靠问题:
- 消息丢失
- 消息重复
- 消息积压
1、消息丢失
产生的原因及相应的解决方案
原因一:
消息发送出去,但由于网络问题没有成功抵达服务器,造成消息丢失。
解决方案:
做好相应的日志记录(将消息信息写入数据库)和容错方法(try-catch 尝试重发、定期扫描数据库将发送失败的消息进行重发)
try{
// 业务代码...
// 将消息信息写入数据库(如:消息id、消息内容、目的地、路由键、类的类型、
// 消息状态[新建、已发、错误抵达、已抵达])
} catch (Exception e) {
// 尝试重发
}
原因二:
消息抵达 Broker 时,MQ 宕机,此时消息未完成持久化(未被写入数据库),造成消息丢失。
解决方案:
使用 publisher(生产者)的确认机制(ConfirmCallback、ReturnCallback)。
public void initRabbitTemplate(RabbitTemplate rabbitTemplate) {
// 设置 消息到达 Broker 的确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
// 消息成功抵达 Broker
}
});
// 设置 消息到达 Queue 的确认回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
// 消息未抵达 Queue,持久化失败
// 消息重发操作...
}
});
}
原因三:
consumer(消费者)收到消息,但还没来得及处理消息就宕机了,造成消息丢失。
解决方案:
开启手动 ACK 模式。
spring:
rabbitmq:
listener:
simple:
# 手动 ack
acknowledge-mode: manual
void messageHandle(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动确认消息
channel.basicAck(deliveryTag, false);
}
2、消息重复消费
产生的原因及相应的解决方案
原因一:
consumer(消费者)消费消息成功,但在回复 ack 时,机器宕机,导致没有成功回复 ack,而 Broker 中的消息状态又重新由 unack 变为 ready,并发送给其它 consumer,导致消息重复消费。
解决方案:
- 保证业务接口的幂等性(重点);
- 使用防重报;
- 查看消息的 redelivered 字段。
原因二(可容忍):
consumer 消费消息失败,触发 Broker 的重试机制, 自动将消息重发出去,导致消息重复消费。
3、消息积压
产生的原因及相应的解决方案
原因:
- consumer 宕机积压,导致消息积压;
- consumer 消费能力不足,导致消息积压;
- publisher 产生消息的速度大于 consumer 消费消息的速度,导致消息积压。
解决方案:
- 上线更多的 consumer;
- 上线专门的队列消费服务,先将消息批量取出,存入数据库,待系统空闲时再从数据库取出消息进行处理。