撸明白分布式事务(四)

2022-09-23 10:51:34 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

前言

在分布式系统中,消息队列在服务端的架构中的地位非常重要,主要解决异步处理、系统解耦、流量削峰等场景。多个系统之间如果同步通信很容易造成阻塞,同时会将这些系统会耦合在一起。因此,引入了消息队列,一方面解决了同步通信机制造成的阻塞,另一方面通过消息队列进行业务解耦。简单的服务间调用引入mq如下图所示

可靠事件模式

可靠事件模式,通过引入可靠的消息队列,只要保证当前的可靠事件投递并且消息队列确保事件传递至少一次,那么订阅这个事件的消费者保证事件能够在自己的业务内被消费即可。这里,请读者思考,是否只要引入了消息队列就可以解决问题了呢?事实上,只是引入消息队列并不能保证其最终的一致性,因为分布式部署环境下都是基于网络进行通信,而网络通信过程中,上下游可能因为各种原因而导致消息丢失。

其一,主业务服务发送消息时可能因为消息队列无法使用而发生失败。对于这种情况,我们可以让主业务服务(生产者)发送消息,再进行业务调用来确保。一般的做法是,主业务服务将要发送的消息持久化到本地数据库,设置标志状态为“待发送”状态,然后把消息发送给消息队列,消息队列收到消息后,也把消息持久化到其存储服务中,但并不是立即向从业务服务(消费者)投递消息,而是先向主业务服务(生产者)返回消息队列的响应结果,然后主业务服务判断响应结果执行之后的业务处理。如果响应失败,则放弃之后的业务处理,设置本地的持久化消息标志状态为“结束”状态。否则,执行后续的业务处理,设置本地的持久化消息标志状态为“已发送”状态。

代码语言:javascript复制
public void doServer(){
    // 发送消息
    send();
    // 执行业务
    exec();
    // 更新消息状态
    updateMsg();
}

正反向消息机制

此外,消息队列发生消息后,也可能从业务服务(消费者)宕机而无法消费。绝大多数消息中间件对于这种情况,例如 RabbitMQ、RocketMQ 等引入了 ACK 机制。注意的是,默认的情况下,采用自动应答,这种方式中消息队列会发送消息后立即从消息队列中删除该消息。所以,为了确保消息的可靠投递,我们通过手动 ACK 方式,如果从业务服务(消费者)因宕机等原因没有发送 ACK,消息队列会将消息重新发送,保证消息的可靠性。从业务服务处理完相关业务后通过手动 ACK 通知消息队列,消息队列才从消息队列中删除该持久化消息。那么,消息队列如果一直重试失败而无法投递,就会出现消息主动丢弃的情况,我们需要如何解决呢?聪明的读者可能已经发现,我们在上个步骤中,主业务服务已经将要发送的消息持久化到本地数据库。因此,从业务服务消费成功后,它也会向消息队列发送一个通知消息,此时它是一个消息的生产者。主业务服务(消费者)接收到消息后,最终把本地的持久化消息标志状态为“完成”状态。说到这里,读者应该可以理解到我们使用“正反向消息机制”确保了消息队列可靠事件投递。当然,补偿机制也是必不可少的。定时任务会从数据库扫描在一定时间内未完成的消息并重新投递。

注意的是,因为从业务服务可能收到消息处理超时或者服务宕机,以及网络等原因导致而消息队列收不到消息的处理结果,因此可靠事件投递并且消息队列确保事件传递至少一次。这里,从业务服务(消费者)需要保证幂等性。如果从业务服务(消费者)没有保证接口的幂等性,将会导致重复提交等异常场景。此外,我们也可以独立消息服务,将消息服务独立部署,根据不同的业务场景共用该消息服务,降低重复开发服务的成本。

了解了“可靠事件模式”的方法论后,现在我们来看一个真实的案例来加深理解。首先,当用户发起退款后,自动化退款服务会收到一个退款的事件消息,此时,如果这笔退款符合自动化退款策略的话,自动化退款服务会先写入本地数据库持久化这笔退款快照,紧接着,发送一条执行退款的消息投递到给消息队列,消息队列接受到消息后返回响应成功结果,那么自动化退款服务就可以执行后续的业务逻辑。与此同时,消息队列异步地把消息投递给退款基础服务,然后退款基础服务执行自己业务相关的逻辑,执行失败与否由退款基础服务自我保证,如果执行成功则发送一条执行退款成功消息投递到给消息队列。最后,定时任务会从数据库扫描在一定时间内未完成的消息并重新投递。这里,需要注意的是,自动化退款服务持久化的退款快照可以理解为需要确保投递成功的消息,由“正反向消息机制”和“定时任务”确保其成功投递。此外,真正的退款出账逻辑在退款基础服务来保证,因此它要保证幂等性,及出账逻辑的收敛。当出现执行失败的状态并且超过重试次数时,就说明这个任务永久失败了,需要开发人员进行手工介入与排查问题。

总结一下,引入了消息队列并不能保证可靠事件投递,换句话说,由于网络等各种原因而导致消息丢失不能保证其最终的一致性,因此,我们需要通过“正反向消息机制”确保了消息队列可靠事件投递,并且使用补偿机制尽可能在一定时间内未完成的消息并重新投递。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/172036.html原文链接:https://javaforall.cn

0 人点赞