分布式事务:XA和Seata的XA模式 | 京东物流技术团队

上一篇内容《从2PC和容错共识算法讨论zookeeper中的Create请求》介绍了保证分布式事务提交的两阶段提交协议,而XA是针对两阶段提交提出的接口实现标准,本文则对XA进行介绍。

1. XA

XA (eXtended Architecture 扩展架构)X/Open组织 提出的 跨异构技术实现两阶段提交 的接口标准。

分布式事务包含两种类型:数据库内部的分布式事务 ,在这种情况下,所有参与事务的节点都运行相同的数据库软件;异构分布式事务,参与者由两种或两种以上的不同数据库软件组成。

它于 1991 年推出并得到了广泛的实现:许多传统关系数据库包括 PostgreSQL、MySQL、DB2、SQL Server 和 Oracle;消息代理包括 ActiveMQ、HornetQ、MSMQ 和 IBM MQ都支持 XA。它不是一个网络协议而是定义了 事务管理器(Transaction Manager)、应用程序(Application Program)资源管理器(Resource Manager) 之间交互的 CAPI(Common Application Programming Interface) 接口标准,如下图所示,但是标准中并没有指明该如何实现,例如在Java EE中,XA使用的是 Java事务API (JTA, Java Transaction API)实现的。

CAPI: 国际标准的通用应用交互接口。

其中三个组件的职责如下:

应用程序(Application Program):负责定义事务的开启、提交或中止,并能够访问事务内的资源(数据库等)

资源管理器(Resource Manager):负责对资源进行管理,相当于两阶段提交中的参与者,能够与事务管理器通过接口交互来传递必要的事务信息

事务管理器(Transaction Manager):负责管理全局事务,分配事务ID,监控事务的执行进度,并负责事务的开启、提交和回滚等,相当于两阶段提交中的协调者

XA同样也分为 准备阶段提交阶段,它对分布式事务管理的流程如下

  • 准备阶段 :AP与TM交互,开启一个 全局分布式事务 ,并发送请求到每个RM,执行数据变更逻辑,此时每个RM会向TM发送请求注册 分支事务 ,在执行完业务逻辑后报告准备提交的状态(事务执行完未提交),之后AP会根据RM的响应在 提交阶段 做出反馈
  • 提交阶段:如果所有的RM都回复"是",表示它们已经准备好提交,那么AP会在该阶段向TM发出提交请求,分布式事务提交;否则,AP会向TM发出中止请求,分布式事务回滚

2. Seata的XA的模式

Seata中有三个角色 事务管理器(Transaction Manager)资源管理器(Resource Manager)事务协调者(Transaction Coordinator) 。在XA模式下,利用事务资源(数据库、消息服务等)对XA协议的支持,来对分布式事务进行管理。

但是,在Seata中三个角色的定义与XA协议标准中角色的定义有所区别:事务管理器(Transaction Manager)应该对应XA协议中的应用程序(Application Program);事务协调者(Transaction Coordinator)对应XA协议中的事务管理器(Transaction Manager)。我认为它们只是在命名上的区别,为了上下文各名词的统一,避免发生因名词不一致而理解混淆的问题,决定以XA标准协议中的定义进行讲解,特此强调

下图是Seata管理分布式事务的流程图,与第一小节中所述事务流程相同,不再赘述。

2.1 XA模式实战分析

有了理论基础,我们以商城系统为例进行简单地演示:订单、商品和购物车分别为三个微服务系统,在执行 下单流程 时会修改商品库存,生成订单和删除购物车中的商品,业务流程代码如下,注意其中包含事务异常回滚的代码

less 复制代码
    @GlobalTransactional
    @Transactional(rollbackFor = Exception.class)
    public void saveOrder(OrderSaveParam orderSaveParam) {
        // 参数校验等必要操作
        // ...

        // 校验商品库存和上架状态
        checkGoodsStatusAndStock(goodsList, goodsCountMap);

        // 修改库存
        reduceGoodsCount(goodsCountMap);

        // 生成订单
        saveOrder(goodsList, goodsCountMap, orderSaveParam.getAddressId());

        // 删除购物车中商品
        shoppingCartService.deleteShoppingCartItem(orderSaveParam.getCartItemIds(), SecurityConstants.INNER);

        // 异常回滚事务
        int i = 1 / 0;
    }

分布式事务执行的 准备阶段,流程图如下

  1. Order Server 在创建订单之前,会向 Seata Server(TM) 注册全局事务,并分配事务ID,对应的控制台日志如下

    less 复制代码
    2023-06-17 22:07:06.479  INFO 74703 --- [io-29009-exec-2] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [127.0.0.1:8091:36427221250506976]
  2. Order Server REST调用 Goods Server 扣减商品数量,Goods Server 在执行数据修改逻辑前会向 Seata Server 注册分支事务,执行完业务逻辑后,并不执行事务提交

  3. Order Server REST调用 ShoppingCart Server 删除购物车中的商品,ShoppingCart Server 在执行数据修改逻辑前会向 Seata Server 注册分支事务,执行完业务逻辑后,同样不执行事务提交

  4. Order Server 本地执行生成订单和其他逻辑

接下来是分布式事务执行的 提交阶段,因生成订单中代码逻辑抛出异常,所以该分布式事务会回滚,OrderServer中对应日志如下

less 复制代码
2023-06-17 22:07:07.029  INFO 74703 --- [io-29009-exec-2] i.s.rm.datasource.xa.ConnectionProxyXA   : 127.0.0.1:8091:36427221250506976-36427221250506978 was rollbacked
2023-06-17 22:07:07.220  INFO 74703 --- [io-29009-exec-2] i.seata.tm.api.DefaultGlobalTransaction  : Suspending current transaction, xid = 127.0.0.1:8091:36427221250506976
2023-06-17 22:07:07.220  INFO 74703 --- [io-29009-exec-2] i.seata.tm.api.DefaultGlobalTransaction  : [127.0.0.1:8091:36427221250506976] rollback status: Rollbacked
  1. Order Server 向 Seata Server 发送 中止请求 ,随后 Seata Server 向 Goods Server 和 ShoppingCart Server 发送 事务回滚请求
  2. Goods Server 和 ShoppingCart Server 收到事务回滚请求后,将各自注册的分支事务回滚,最终全局分布式事务回滚,以保证数据的一致性 。Goods Server 分支事务回滚对应的日志如下,可以发现分支事务的ID为全局事务ID-分支ID,并显示PhaseTwo_Rollbacked 在阶段二回滚
less 复制代码
2023-06-17 22:07:07.081  INFO 74680 --- [h_RMROLE_1_4_24] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=127.0.0.1:8091:36427221250506976,branchId=36427221250506986,branchType=XA,resourceId=jdbc:mysql://127.0.0.1:3306/fy_mall_goods,applicationData=null
2023-06-17 22:07:07.081  INFO 74680 --- [h_RMROLE_1_4_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 127.0.0.1:8091:36427221250506976 36427221250506986 jdbc:mysql://127.0.0.1:3306/fy_mall_goods
2023-06-17 22:07:07.096  INFO 74680 --- [h_RMROLE_1_4_24] i.s.rm.datasource.xa.ResourceManagerXA   : 127.0.0.1:8091:36427221250506976-36427221250506986 was rollbacked
2023-06-17 22:07:07.096  INFO 74680 --- [h_RMROLE_1_4_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked

注:如果有朋友想试试Seata的XA模式,可以参考示例代码仓库FangYuan33/book-spring-cloud,对应的方法入口为 /saveOrder

3. 对XA的思考

XA能够保持多个参与者数据相互一致,但是同时也引入了比较严重的运维问题

因为如果协调者宕机,那么其中已经 准备但未提交事务 的所有参与者都会被阻塞。被阻塞的根本是 ,例如在读已提交隔离级别上,数据库事务通常会获取到待修改行数据的 行级排他锁 来防止脏写。在分布式事务提交或中止前,参与者数据库不能释放这些锁,因此协调者宕机多久,这些锁就要持有多久(在没有认为干预的情况下)。这些锁被持有的期间,导致其他事务不能修改这些数据(根据数据库的不同,读取操作也可能被阻塞),所以这些数据相关的业务都会被阻塞,导致应用大面积的不可用,直至 存疑事务 被解决(提交/中止)。

理论上,如果协调者崩溃并重新启动,它应该从日志中恢复事务的状态,并解决现存的疑虑事务,但是在实际生产中,仍然会有疑虑事务的出现(可能是事务日志被破坏)。也许你可能会考虑将相关应用的数据库服务器重启,但是在2PC正确的实现中,为了 原子性 的保证,重启后也必须持有存疑事务的锁 。那么这样 唯一的解决方案 是让管理员手动提交还是回滚事务,这是引入运维问题的所在。不过,许多XA事务的实现都有一个叫做 启发式决策 的逃生出口,允许参与者单方面决定提交或放弃一个存疑事务,而无需等待协调者的决定,但是这也正是避免灾难性情况的手段,而不是为了日常的使用,因为这种方式有可能会破坏事务的原子性。

所以,协调者的 高可用 是需要我们考虑的问题,它本身也是一种数据库(保存了事务的结果),需要像其他应用数据库服务一样被认真的对待。

巨人的肩膀

作者:京东物流 王奕龙

来源:京东云开发者社区 自猿其说Tech 转载请注明来源

相关推荐
欧先生^_^9 分钟前
Istio 的授权认证 和 OAuth2/OIDC
数据库·sql·istio
00后程序员张1 小时前
将iOS上架流程融入DevOps体系:从CI构建到App Store发布的完整实践
websocket·网络协议·tcp/ip·http·网络安全·https·udp
bloglin999993 小时前
创建postgres数据库失败
数据库·sql·oracle
污领巾4 小时前
虚幻引擎UE多语言设计与实现
java·数据库·虚幻
network_tester8 小时前
路由器压测实战:从负载均衡到DDoS防御,5步定位性能瓶颈(附脚本工具包)
网络·网络协议·tcp/ip·http·网络安全·https·信息与通信
掘根9 小时前
【MySQL进阶】常用MySQL程序
数据库·mysql
Edingbrugh.南空9 小时前
Kafka 3.0零拷贝技术全链路源码深度剖析:从发送端到日志存储的极致优化
分布式·kafka
PH = 710 小时前
MySQL的Sql优化经验总结
数据库·mysql
Code季风10 小时前
跨语言RPC:使用Java客户端调用Go服务端的HTTP-RPC服务
java·网络协议·http·rpc·golang
fouryears_2341710 小时前
Mysql初阶操作:对命令的详细介绍
数据库·mysql