前言
目前微服务的火热程度不用多说,简历上不写个熟悉Spring Cloud的框架感觉都没有竞争力。但是玩了这么久的微服务,其中关键的分布式事务问题,你真的掌握了吗?
今天带大家使用seata xa模式轻松解决分布式事务问题。
一、什么是seata?
Seata 是阿里打造的一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
二、seata原理说明
1、角色说明
Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
2、什么是 Seata 的事务模式?
Seata 定义了全局事务的框架。
全局事务 定义为若干 分支事务 的整体协调:
1、TM 向 TC 请求发起(Begin)、提交(Commit)、回滚(Rollback)全局事务。
2、TM 把代表全局事务的 XID 绑定到分支事务上。
3、RM 向 TC 注册,把分支事务关联到 XID 代表的全局事务中。
4、RM 把分支事务的执行结果上报给 TC。(可选)
5、TC 发送分支提交(Branch Commit)或分支回滚(Branch Rollback)命令给 RM。
Seata 的 全局事务 处理过程,分为两个阶段:
1、执行阶段 :执行 分支事务,并 保证 执行结果满足是 可回滚的(Rollbackable) 和 持久化的(Durable)。
2、完成阶段: 根据 执行阶段 结果形成的决议,应用通过 TM 发出的全局提交或回滚的请求给 TC,TC 命令 RM 驱动 分支事务 进行 Commit 或 Rollback。
Seata 的所谓 事务模式 是指:运行在 Seata 全局事务框架下的 分支事务 的行为模式。准确地讲,应该叫作 分支事务模式。
不同的 事务模式 区别在于 分支事务 使用不同的方式达到全局事务两个阶段的目标。即,回答以下两个问题:
1、执行阶段 :如何执行并 保证 执行结果满足是 可回滚的(Rollbackable) 和 持久化的(Durable)。
2、完成阶段: 收到 TC 的命令后,如何做到分支的提交或回滚?
以我们 Seata 的 AT 模式和 TCC 模式为例来理解:
AT 模式:
执行阶段:
可回滚:根据 SQL 解析结果,记录回滚日志
持久化:回滚日志和业务 SQL 在同一个本地事务中提交到数据库
完成阶段:
分支提交:异步删除回滚日志记录
分支回滚:依据回滚日志进行反向补偿更新
TCC 模式:
执行阶段
调用业务定义的 Try 方法(完全由业务层面保证 可回滚 和 持久化)
完成阶段:
分支提交:调用各事务分支定义的 Confirm 方法
分支回滚:调用各事务分支定义的 Cancel 方法
三、SEATA 的分布式案例
1、业务逻辑说明
用户购买商品的业务逻辑。整个业务逻辑由4个微服务提供支持:
交易服务(Business):购买商品,发起交易,开启全局事务,作为TM
仓储服务(Storage):对给定的商品扣除仓储数量。作为RM
订单服务(Order):根据采购需求创建订单。作为RM
帐户服务(Account):从用户帐户中扣除余额。作为RM
seate server作为TC事务协调者,维护全局和分支事务的状态
2、架构图
3、SEATA 的分布式交易解决方案
具体的执行流程如下:
1、交易服务接收到购买请求,作为TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。
2、交易服务向库存服务(Storage)和订单服务(Order)发起RPC请求。(XID在微服务调用链路的上下文中传播)
3、库存服务(Storage)和订单服务(Order)向TC注册分支事务,并执行扣减库存,生产订单的业务逻辑,并将其纳入XID对应的全局事务的管辖。
4、订单服务(Order)向账户服务(Account)发起请求扣减账户余额,账户服务(Account)向TC注册分支事务,执行扣减账户余额操作。
5、账户服务(Account),订单服务(Order),库存服务(Storage)分支事务执行完毕。
6、TM向TC发起针对XID的全局提交或回滚决议。
7、TC调度XID下管辖的全部分支事务完成提交或回滚请求。
四、Seata XA模式说明
Seata 1.2.0 版本重磅发布新的事务模式:XA 模式,实现对 XA 协议的支持。
1、什么是 XA?
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。
XA 规范 描述了全局的事务管理器与局部的资源管理器之间的接口。XA规范 的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使 ACID 属性跨越应用程序而保持有效。
XA 规范 使用两阶段提交(2PC,Two-Phase Commit)来保证所有资源同时提交或回滚任何特定的事务。
XA 规范 在上世纪 90 年代初就被提出。目前,几乎所有主流的数据库都对 XA 规范 提供了支持。
2、什么是 Seata 的 XA 模式?
XA 模式:
在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。
执行阶段:
可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚
持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)
完成阶段:
分支提交:执行 XA 分支的 commit
分支回滚:执行 XA 分支的 rollback
3、为什么支持 XA ?
为什么要在 Seata 中增加 XA 模式呢?支持 XA 的意义在哪里呢?
1、补偿型事务模式的问题
本质上,Seata 已经支持的 3 大事务模式:AT、TCC、Saga 都是 补偿型 的。
补偿型 事务处理机制构建在 事务资源 之上(要么在中间件层面,要么在应用层面),事务资源 本身对分布式事务是无感知的。
事务资源 对分布式事务的无感知存在一个根本性的问题:无法做到真正的 全局一致性 。
比如,一条库存记录,处在 补偿型 事务处理过程中,由 100 扣减为 50。此时,仓库管理员连接数据库,查询统计库存,就看到当前的 50。之后,事务因为异外回滚,库存会被补偿回滚为 100。显然,仓库管理员查询统计到的 50 就是 脏 数据。
可以看到,补偿型 分布式事务机制因为不要求 事务资源 本身(如数据库)的机制参与,所以无法保证从事务框架之外的全局视角的数据一致性。
2、XA 的价值
与 补偿型 不同,XA 协议 要求 事务资源 本身提供对规范和协议的支持。
因为 事务资源 感知并参与分布式事务处理过程,所以 事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。
比如,上一节提到的库存更新场景,XA 事务处理过程中,中间态数据库存 50 由数据库本身保证,是不会仓库管理员的查询统计 看 到的。(当然隔离级别需要 读已提交 以上)
除了 全局一致性 这个根本性的价值外,支持 XA 还有如下几个方面的好处:
1、业务无侵入:和 AT 一样,XA 模式将是业务无侵入的,不给应用设计和开发带来额外负担。
2、数据库的支持广泛:XA 协议被主流关系型数据库广泛支持,不需要额外的适配即可使用。
3、多语言支持容易:因为不涉及 SQL 解析,XA 模式对 Seata 的 RM 的要求比较少,为不同语言开发 SDK 较之 AT 模式将更 薄,更容易。
4、传统基于 XA 应用的迁移:传统的,基于 XA 协议的应用,迁移到 Seata 平台,使用 XA 模式将更平滑。
4、XA 模式的前提
支持XA 事务的数据库。
Java 应用,通过 JDBC 访问数据库。
5、XA 整体机制
在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。
执行阶段:
可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚
持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)
完成阶段:
分支提交:执行 XA 分支的 commit
分支回滚:执行 XA 分支的 rollback
6、XA 工作机制
1. 整体运行机制
XA 模式 运行在 Seata 定义的事务框架内:
执行阶段(E xecute):
XA start/XA end/XA prepare + SQL + 注册分支
完成阶段(F inish):
XA commit/XA rollback
2. 数据源代理
XA 模式需要 XAConnection。
五、seata server安装
1、下载最新版本的 Seata Sever
这里我下载的是最新版seata-server-1.4.1
2、解压并启动 Seata server
unzip seata-server-1.4.1.zip cd seate/bin sh seata-server.sh -p 8091 -h 127.0.0.1 -m file
配置暂时不做任何修改。默认会采用file存储模式。
六、项目实战
可以参考 Seata 官网的样例:seata-xa
不知道大家下载后能否运行,反正我是折腾半天,还是启动失败。最后参考着自己对项目进行重建。
1、执行sql/all_in_one.sql,建立相关业务表
sql存放的地址:all_in_one.sql
2、构建工程seata-xa
3、maven依赖
需要注意一下问题:
3.1 Spring boot和Spring Cloud的版本对应关系
这里我采用的是Spring Boot 2.2.6.RELEASE,Spring Cloud采用的Hoxton.SR10
3.2 没有采用官网demo中的spring-cloud-alibaba-seata,而是seata-spring-boot-starter 1.4.1
经尝试采用spring-cloud-alibaba-seata时,项目启动一直报错,暂时没有找到解决办法。
seata-spring-boot-starter的版本选用1.4.1,这样才支持DataSourceProxyXA。并且可以和seata-server-1.4.1版本对应上。
4、添加seate相关属性
seata.tx-service-group=my_test_tx_group seata.data-source-proxy-mode=XA seata.registry.type=file
属性说明:
seata.tx-service-group属性是配置seata事务服务的分组名称,需要和seate server的配置保持一直。默认值为my_test_tx_group。
seata.data-source-proxy-mode属性用来指定数据源的代理模式,支持AT和XA,如果选择AT模式的话,需要在数据源中创建undo_log事务回滚日志表。
seata.registry.type属性用来指定seata的注册中心的类型,目前支持file,nacos,eureka,redis,zk等多种模式,默认为file。
5、数据源的配置类都可以省略
seata-spring-boot-starter会自动根据配置seata.data-source-proxy-mode启用不同的数据源代理。
不需要在代码中进行控制。
@Bean("dataSource") public DataSource dataSource(DruidDataSource druidDataSource) { // DataSourceProxy for AT mode // return new DataSourceProxy(druidDataSource); // DataSourceProxyXA for XA mode return new DataSourceProxyXA(druidDataSource); }
6、在发起分布式事务请求的方法上添加@GlobalTransactional 注解,开启全局事务
需要使用一个 @GlobalTransactional 注解在业务方法上:
@GlobalTransactional public void purchase(String userId, String commodityCode, int orderCount) { ...... }
7、在发起分布式事务请求的方法上添加@GlobalTransactional 注解,开启全局事务
需要使用一个 @GlobalTransactional 注解在业务方法上:
@GlobalTransactional public void purchase(String userId, String commodityCode, int orderCount) { ...... }
8、分支事务上,添加@Transactional注解
@Transactional(rollbackFor = Exception.class) public void reduce(String userId, int money) { String xid = RootContext.getXID(); log.info("reduce account balance in transaction: " + xid); jdbcTemplate.update("update account_tbl set money = money - ? where user_id = ?", new Object[] {money, userId}); int balance = jdbcTemplate.queryForObject("select money from account_tbl where user_id = ?", new Object[] {userId}, Integer.class); log.info("balance after transaction: " + balance); if (balance < 0) { throw new RuntimeException("Not Enough Money ..."); } }
9、启动四个项目
10、发起请求校验分布式事务
发起购买请求:http://127.0.0.1:8020/purchase
基于初始化数据,和默认的调用逻辑,purchase 将可以被成功调用 3 次。
每次账户余额扣减 3000,由最初的 10000 减少到 1000。
第 4 次调用,因为账户余额不足,purchase 调用将失败。相应的:库存、订单、账户都回滚。
项目地址:seata-xa
总结
本文主要介绍了seata xa的工作原理和实战使用方法。
相比其他seata其他3中模式,xa模式最大的优点就是业务无侵入,满足全局数据一致性。但相应的,xa模式需要数据库支持XA协议,性能较差,对长事务支持较差。
在当前的技术发展阶段,不存一个分布式事务处理机制可以完美满足所有场景的需求。
一致性、可靠性、易用性、性能等诸多方面的系统设计约束,需要用不同的事务处理机制去满足。
Seata 项目最核心的价值在于:构建一个全面解决分布式事务问题的标准化平台。
XA 模式的加入,补齐了 Seata 在 全局一致性 场景下的缺口,形成 AT、TCC、Saga、XA 四大 事务模式 的版图,基本可以满足所有场景的分布式事务处理诉求。