消息可靠性问题
用mq做消息代理,当mq的信息发送不到交易服务时,会造成消息的丢失问题。
消息丢失的可能性:
1.支付服务到消息代理的过程中消息丢失。
2.消息代理(MQ)导致消息丢失。
3.消息代理到交易服务过程中消息丢失。
生产者重连
有时候由于网络波动,可能会出现客户端连接MQ失败的情况。通过配置我们可以开启连接失败后的重连机制:
spring:
rabbitmq:
connection-timeout: 1s #设置mq的连接超时时间
template:
retry:
enabled: true #开启超时重试机制
initial-interval: 1000ms #失败后的初始等待时间
multiplier: 2 #失败后下次的等待时长倍数,下次等待时长 = initial-interval * multiplier
max-attempts: 3 #最大重试次数
注意:
当网络不稳定的时候,利用重试机制可以有效提高消息发送的成功率。不过SpringAMQP提供的重试机制是阻塞式的重试,也就是说多次重试等待的过程中,当前线程是被阻塞的,会影响业务性能。
如果对于业务性能有要求,建议禁用重试机制。如果一定要使用,请合理配置等待时长和重试次数,当然也可以考虑使用异步线程来执行发送消息的代码。
这里时间间隔2s,3s下一次的间隔必为5s
生产者确认
RabbitMQ给出了Publisher Confirm和Publisher Return两种确认机制.开启确认机制后,在MQ成功收到消息后会返回确认消息给生产者。返回的结果有以下几种情况。
- 消息投递到了MQ,但是路由失败。此时会通过PublisherReturn返回路由异常原有,然后返回ACK,告知投递成功。
- 临时消息投递到MQ,并且入队成功,返回ACK,告知投递成功
- 持久消息投递到了MQ,并且入队完成持久化,返回ACK,告知投递成功。
- 其他情况都会返回NACK,告知投递失败。
SpringAMQP实现生产者确认
1.在publisher这个微服务的application.yml中添加配置
spring:
rabbitmq:
publisher-confirm-type: correlated
publisher-returns: true
配置说明:
- 这里publisher-confirm-type有三种模式可选:
none: 关闭confirm机制
simple: 同步阻塞等待MQ的回执消息
correlated: MQ异步回调方式返回回执消息
2.增加配置bean
@Configuration
@Slf4j
public class CommonConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//获取rabbitTemplate
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
//设置ReturnCallback
rabbitTemplate.setReturnCallback((message,replyCode,replyText,exchange,routingKey)->{
log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",replyCode,replyText,exchange,routingKey,message);
});
}
}
随着版本迭代更新,也可以换一种写法:
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//获取rabbitTemplate
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
//设置ReturnCallback
rabbitTemplate.setReturnsCallback(rmsg -> log.info("消息发送失败,应答码是{},原因是{},交换机是{},路由键是{},消息是{}",rmsg.getReplyCode()
,rmsg.getReplyText(),rmsg.getExchange(),rmsg.getRoutingKey(),rmsg.getMessage()));
}
3.调用时增加
@Test
public void testPayQueue() {
//1.创建CorrelationData
CorrelationData cd = new CorrelationData();
//2.给Future添加ConfirmCallback
cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
@Override
public void onFailure(Throwable ex) {
//Future发生异常时的处理逻辑,基本不会触发
log.info("handle message ack fail",ex);
}
@Override
public void onSuccess(CorrelationData.Confirm result) {
if (result.isAck()){
log.info("发送消息成功,收到ack!");
}else {
log.error("发送消息失败,收到nack,reason:{}",result.getReason());
}
}
});
//发送消息
rabbitTemplate.convertAndSend("pay.topic","pay.success","202405200002",cd);
// System.out.println("这里是阻塞式的重试");
}
总结
只要消息到达了mq就是ack
如何处理生产者的确认消息?
- 生产者确认需要额外的网络和系统资源开销,尽量不要使用
- 如果一定要使用,无需开启Publisher-Return机制,因为一般路由失败是自己业务问题
- 对于nack消息可以有限次数重试,依然失败则记录异常消息