分布式系统架构系列讲解十四(分布式事物 4):可靠消息最终一致性方案

本文介绍了分布式系统中可靠消息最终一致性方案,通过本地消息表和分布式消息中间件(如RocketMQ)实现事务的最终一致性。详细阐述了本地消息表的生产者、消费者和可靠消息服务的运作过程,并分析了RocketMQ的Prepare、确认阶段和ACK机制。此方案适用于异步服务调用,降低了业务系统与消息系统的耦合,但存在额外的系统复杂性和性能影响。
摘要由CSDN通过智能技术生成

分布式系统架构系列讲解 - 总目录

一、简介

本章,我们将要介绍一种生产上最常用的分布式事务解决方案——可靠消息最终一致性方案。所谓可靠消息最终一致性方案,其实就是在分布式系统当中,把一个业务操作转换成一个消息,然后利用消息来实现事务的最终一致性

比如从A账户向B账户转账的操作,当服务A从A账户扣除完金额后,通过消息中间件向服务B发一个消息,服务B收到这条消息后,进行B账户的金额增加操作。

可靠消息最终一致性方案一般有两种实现方式,原理其实是一样的:

  • 基于本地消息表
  • 基于支持分布式事务的消息中间件,如RocketMQ等

二、本地消息表

基于本地消息表的分布式事务,是最简便的实现方式,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于eBay。

我们来看下面这张图,基于本地消息服务的分布式事务分为三大部分:

  • 可靠消息服务:存储消息,因为通常通过数据库存储,所以也叫本地消息表

  • 生产者(上游服务):生产者是接口的调用方,生产消息

  • 消费者(下游服务):消费者是接口的服务方,消费消息

在这里插入图片描述

2.1 可靠消息服务

可靠消息服务就是一个单独的服务,有自己的数据库,其主要作用就是存储消息(包含接口调用信息,全局唯一的消息编号),消息通常包含以下状态:

  • 待确认:上游服务发送待确认消息
  • 已发送:上游服务发送确认消息
  • 已取消(终态):上游服务发送取消消息
  • 已完成(终态):下游服务确认接口执行完成

2.2 生产者

服务调用方(消息生产者)需要调用下游接口时,不直接通过RPC之类的方式调用,而是先生成一条消息,其主要步骤如下:

  1. 生产者调用接口前,先发送一条待确认消息(一般称为half-msg,包含接口调用信息)给可靠消息服务,可靠消息服务会将这条记录存储到自己的数据库(或本地磁盘),状态为【待确认】;
  2. 生产者执行本地事务,本地事务执行成功并提交后,向可靠消息服务发送一条确认消息;如果本地执行失败,则向消息服务发送一条取消消息;
  3. 可靠消息服务如果收到消息后,修改本地数据库中的那条消息记录的状态改为【已发送】或【已取消】。如果是确认消息,则将消息投递到MQ消息队列;(修改消息状态和投递MQ必须在一个事务里,保证要么都成功要么都失败

为了防止出现:生产者的本地事务执行成功,但是发送确认/取消消息超时的情况。可靠消息服务里一般会提供一个后台定时任务,不停的检查消息表中那些【待确认】的消息,然后回调生产者(上游服务)的一个接口,由生产者确认到底是取消这条消息,还是确认并发送这条消息。

在这里插入图片描述

通过上面这套机制,可以保证生产者对消息的100%可靠投递。

2.3 消费者

服务提供方(消息消费者),从MQ消费消息,然后执行本地事务。执行成功后,反过来通知可靠消息服务,说自己处理成功了,然后可靠消息服务就会把本地消息表中的消息状态置为最终状态【已完成】 。

这里要注意两种情况:

  1. 消费者消费消息失败,或者消费成功但执行本地事务失败。针对这种情况,可靠消息服务可以提供一个后台定时任务,不停的检查消息表中那些【已发送】但始终没有变成【已完成】的消息,然后再次投递到MQ,让下游服务来再次处理。也可以引入zookeeper,由消费者通知zookeeper,生产者监听到zookeeper上节点变化后,进行消息的重新投递。
  2. 如果消息重复投递,消息者的接口逻辑需要实现幂等性,保证多次处理一个消息不会插入重复数据或造成业务数据混乱。针对这种情况,消费者可以准备一张消息表,用于判重。消费者消费消息后,需要去本地消息表查看这条消息有没处理成功,如果处理成功直接返回成功。

在这里插入图片描述

2.4 总结

这个方案的优点是简单,但最大的问题在于可靠消息服务是严重依赖于数据库的,即通过数据库的消息表来管理事务,不太适合并发量很高的场景。

三、分布式消息中间件

许多开源的消息中间件都支持分布式事务,比如RocketMQ、Kafka。其思想几乎是和本地消息表/服务是一样的,只不过是将可靠消息服务和MQ功能封装在一起,屏蔽了底层细节,从而更方便用户的使用。这种方案有时也叫做可靠消息最终一致性方案。

以RocketMQ为例,消息的发送分成2个阶段:Prepare阶段确认阶段

在这里插入图片描述

3.1 prepare阶段

  1. 生产者发送一个不完整的事务消息——HalfMsg到消息中间件,消息中间件会为这个HalfMsg生成一个全局唯一标识,生产者可以持有标识,以便下一阶段找到这个HalfMsg;
  2. 生产者执行本地事务。

**注意:**消费者无法立刻消费HalfMsg,生产者可以对HalfMsg进行Commit或者Rollback来终结事务。只有当Commit了HalfMsg后,消费者才能消费到这条消息。

3.2 确认阶段

  1. 如果生产者执行本地事务成功,就向消息中间件发送一个Commit消息(包含之前HalfMsg的唯一标识),中间件修改HalfMsg的状态为【已提交】,然后通知消费者执行事务;
  2. 如果生产者执行本地事务失败,就向消息中间件发送一个Rollback消息(包含之前HalfMsg的唯一标识),中间件修改HalfMsg的状态为【已取消】。

消息中间件会定期去向生产者询问,是否可以Commit或者Rollback那些由于错误没有被终结的HalfMsg,以此来结束它们的生命周期,以达成事务最终的一致。之所以需要这个询问机制,是因为生产者可能提交完本地事务,还没来得及对HalfMsg进行Commit或者Rollback,就挂掉了,这样就会处于一种不一致状态。

3.3 ACK机制

消费者消费完消息后,可能因为自身异常,导致业务执行失败,此时就必须要能够重复消费消息。RocketMQ提供了ACK机制,即RocketMQ只有收到服务消费者的ack message后才认为消费成功。

所以,服务消费者可以在自身业务员逻辑执行成功后,向RocketMQ发送ack message,保证消费逻辑执行成功。

四、示例

我们最后以一个电子商务支付系统的核心交易链路为示例,来更好的理解下可靠消息最终一致性方案。

4.1 交易链路

假设我们的系统的核心交易链路如下图。用户支付订单时,首先调用订单服务的对外接口服务,然后开始核心交易链路的调用,依次经过订单业务服务、库存服务、积分服务,全部成功后再通过MQ异步调用仓储服务:

在这里插入图片描述

上图中,订单业务服务、库存服务、积分服务都是同步调用的,由于是核心链路,我们可以通过上一章中讲解的TCC分布式事务来保证分布式事务的一致性。而调用仓储服务可以异步执行,所以我们依赖RocketMQ来实现分布式事务。

4.2 事务执行

接着,我们来看下引入RocketMQ来实现分布式事务后,整个系统的业务执行流程发生了哪些变化,整个流程如下图:

在这里插入图片描述

  1. 当用户针对订单发起支付时,首先订单接口服务先发送一个half-msg消息给RocketMQ,收到RocketMQ的成功响应(注意,此时仓储服务还不能消费消息,因为half-msg还没有确认)。
  2. 然后,订单接口服务调用核心交易链路,如果其中任一服务执行失败,则先执行内部的TCC事务回滚;
  3. 如果订单接口服务收到链路失败的响应,则向MQ投递一个rollback消息,取消之前的half-msg;
  4. 如果订单接口服务收到链路成功的响应,则向MQ投递一个commit消息,确认之前的half-msg,那仓库服务就可以消费消息;
  5. 仓储服务消费消息成功并执行完自身的逻辑后,会向RocketMQ投递一个ack message,以确保消费成功。

注意,如果因为网络原因,导致RocketMQ始终没有收到订单接口服务对half-msg的commit或rollback消息,RocketMQ就会回调订单接口服务的某个接口,以查询该half-msg究竟是进行commit还是rollback。

五、总结

可靠消息最终一致性方案,一般适用于异步的服务调用,比如支付成功后,调用积分服务进行积分累加、调用库存服务进行发货等等。总结一下,可靠消息最终一致性方案其实最基本的思想就两点:

  1. 通过引入消息中间件,保证生产者对消息的100%可靠投递;
  2. 通过引入Zookeeper,保证消费者能够对未成功消费的消息进行重新消费(消费者要保证自身接口的幂等性)。

5.1 优缺点

可靠消息最终一致性方案是目前业务主流的分布式事务落地方案,其优缺点主要如下:

优点: 消息数据独立存储,降低业务系统与消息系统间的耦合。

缺点: 一次消息发送需要两次请求,业务服务需要提供消息状态查询的回调接口。

一般来讲,99%的分布式接口调用不需要做分布式事务,通过监控(邮件、短信告警)、记录日志,就可以事后快速定位问题,然后就是排查、出解决方案、修复数据。

因为用分布式事务一定是有成本的,而且这个成本会比较高,特别是对于一些中小型公司。同时,引入分布式事务后,代码复杂度、开发周期会大幅上升,系统性能和吞吐量会大幅下跌,这就导致系统更加更加脆弱,更容易出bug。当然,如果有资源能够持续投入,分布式事务做好了的话,好处就是可以100%保证数据一致性不会出错。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1、课程简介Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。       在本套课程中,我们将全面的讲解Spring Cloud技术栈, 从环境的部署到技术的应用,再到项目实战,让我们不仅是学习框架技术的使用,而且可以学习到使用Spring Cloud如何解决实际的问题。Spring Cloud各个组件相互配合,合作支持了一套完整的微服务架构。- 注册中心负责服务的注册与发现,很好将各服务连接起来- 断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护。- API网关负责转发所有对外的请求和服务- 配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息- 链路追踪技术可以将所有的请求数据记录下来,方便我们进行后续分析- 各个组件又提供了功能完善的dashboard监控平台,可以方便的监控各组件的运行状况2、适应人群有一定的Java基础,并且要有一定的web开发基础。3、课程亮点       系统的学习Spring Cloud技术栈,由浅入深的讲解微服务技术。涵盖了基础知识,原理剖析,组件使用,源码分析,优劣分析,替换方案等,以案例的形式讲解微服务中的种种问题和解决方案l  微服务的基础知识n  软件架构的发展史n  微服务的核心知识(CAP,RPC等)l  注册中心n  Eureka搭建配置服务注册n  Eureka服务端高可用集群n  Eureka的原理和源码导读n  Eureka替换方案Consuln  Consul下载安装&服务注册&高可用l  服务发现与服务调用n  Ribbon负载均衡基本使用&源码分析n  Feign的使用与源码分析n  Hystrix熔断(雪崩效应,Hystrix使用与原理分析)n  Hystrix替换方案Sentinell  微服务网关n  Zuul网关使用&原理分析&源码分析n  Zuul 1.x 版本的不足与替换方案n  SpringCloud Gateway深入剖析l  链路追踪n  链路追踪的基础知识n  Sleuth的介绍与使用n  Sleuth与Zipkin的整合开发l  配置中心n  SpringClond Config与bus 开发配置中心n  开源配置中心Apollo4、主讲内容章节一:1.     微服务基础知识2.     SpringCloud概述3.     服务注册中心Eureka4.     Eureka的替换方案Consul章节二:1.     Ribbon实现客户端负载均衡2.     基于Feign的微服务调用3.     微服务熔断技术Hystrix4.     Hystrix的替换方案Sentinel章节三:1.     微服务网关Zuul的基本使用2.     Zuul1.x 版本的不足和替换方案3.     深入SpringCloud Gateway4.     链路追踪Sleuth与Zipkin章节四:1.     SpringCloud Config的使用2.     SpringCloud Config结合SpringCloud Bus完成动态配置更新3.     开源配置中心Apollo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃透Java

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值