简介
seata:Simple Extensible Autonomous Transaction Architecture
官网:http://seata.io
github:https://github.com/seata/seata
2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback),和社区一起共建开源分布式事务解决方案。Fescar 的愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题。
Fescar 开源后,蚂蚁金服加入 Fescar 社区参与共建,并在 Fescar 0.4.0 版本中贡献了 TCC 模式。
为了打造更中立、更开放、生态更加丰富的分布式事务开源社区,经过社区核心成员的投票,大家决定对 Fescar 进行品牌升级,并更名为 Seata,意为:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案。
Seata 融合了阿里巴巴和蚂蚁金服在分布式事务技术上的积累,并沉淀了新零售、云计算和新金融等场景下丰富的实践经验,但要实现适用于所有的分布式事务场景的愿景,仍有很长的路要走。因此,我们决定建立一个完全中立的分布式事务组织,希望更多的企业、开发者能够加入我们,一起打造 Seata。
历史:
Ant Financial
XTS:Extended Transaction Service,可扩展事务服务。蚂蚁金服中间件团队自2007年以来开发了分布式事务中间件,广泛应用于Ant Financial,解决了跨数据库和服务的数据一致性问题。
DTX:Distributed Transaction Extended。自2013年以来,XTS已在Ant Financial Cloud上发布,名称为DTX。
阿里巴巴
TXC:Taobao Transaction Constructor。阿里巴巴中间件团队自2014年起启动该项目,以解决因应用程序架构从单片机改为微服务而导致的分布式事务问题。
GTS:Global Transaction Service。 TXC作为Aliyun中间件产品,新名称GTS自2016年起发布。
Fescar:我们从2019年开始基于TXC / GTS开源开源项目Fescar,以便在未来与社区密切合作。
Seata社区
Seata:简单的可扩展自治交易架构。 Ant Financial加入Fescar,使其成为一个更加中立和开放的分布式服务社区,并将Fescar更名为Seata。
结构
Seata有3个基本组件:
- Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
- Transaction Manager™:事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
- Resource Manager(RM):资源管理器,控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
全局事务与分支事务:
a Distributed Transaction is a Global Transaction which is made up with a batch of Branch Transaction, and normally Branch Transaction is just Local Transaction.
Seata管理分布式事务的典型生命周期:
- TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
- XID 在微服务调用链路的上下文中传播。
- RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
- TM 向 TC 发起针对 XID 的全局提交或回滚决议。
- TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
至此,seata的协议机制总体上看与 XA 是一致的。但是是有差别的:
XA 方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身(通过提供支持 XA 的驱动程序来供应用使用)。
而 Fescar 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的,不依赖于数据库本身对协议的支持,当然也不需要数据库支持 XA 协议。这点对于微服务化的架构来说是非常重要的:应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。
这个设计,剥离了分布式事务方案对数据库在 协议支持 上的要求。
快速入门
springCloud整合seata:https://github.com/seata/seata-samples/tree/master
官方提供了案例工程(这里选择的是JPA):
案例工程结构如下:
测试该实例
- 1、首先我们修改每个微服务中的数据源配置
-
spring.application.name=order-service server.port=8082 spring.datasource.url=jdbc:mysql://rm-2zetd9474ydd1g5955o.mysql.rds.aliyuncs.com:3306/fescar?useSSL=false&serverTimezone=UTC spring.datasource.username=workshop spring.datasource.password=Workshop123 spring.jpa.show-sql=true
-
- 2、创建所需的数据库
-
# Account DROP SCHEMA IF EXISTS db_account; CREATE SCHEMA db_account; USE db_account; CREATE TABLE `account_tbl` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `user_id` VARCHAR(255) DEFAULT NULL, `money` INT(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; INSERT INTO account_tbl (id, user_id, money) VALUES (1, '1001', 10000); INSERT INTO account_tbl (id, user_id, money) VALUES (2, '1002', 10000); CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; # Order DROP SCHEMA IF EXISTS db_order; CREATE SCHEMA db_order; USE db_order; CREATE TABLE `order_tbl` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `user_id` VARCHAR(255) DEFAULT NULL, `commodity_code` VARCHAR(255) DEFAULT NULL, `count` INT(11) DEFAULT '0', `money` INT(11) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; # Storage DROP SCHEMA IF EXISTS db_storage; CREATE SCHEMA db_storage; USE db_storage; CREATE TABLE `storage_tbl` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `commodity_code` VARCHAR(255) DEFAULT NULL, `count` INT(11) DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `commodity_code` (`commodity_code`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; INSERT INTO storage_tbl (id, commodity_code, count) VALUES (1, '2001', 1000); CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
- 3、启动seata
- 4、依次启动四个微服务
- 正常提交情况: 在浏览器中输入:http://127.0.0.1:8084/purchase/commit
- 测试结果:
business-service源码,其实就是1001账户花了5金钱购买了2001物品。前后数据一致
- 测试结果:
- 异常回滚情况: 在浏览器访问:http://127.0.0.1:8084/purchase/rollback
-
查看数据库,会发现各个表数据不变!!
-
查看控制台,account-service、order-service、business-service报错!这是因为business-service的rollback请求的业务(BusinessController)是这样的:
-
在account-service的AccountService中判断账号是否是1002,是就抛出运行时异常。
-
总结流程:浏览器访问/rollback --> business-service的Controller让1002账户买东西 --> order-service创建订单,并远程调用1002的账户信息扣款 --> account-service扣款,并在最后制造一个运行时异常。
-
导致:account-service异常 --> 调用方order-service异常 --> 调用方business-service异常。
-
但是:storage-service执行正常。
-
由于business-service使用了分布式事务,所以storage-service数据回滚。最后都没有数据更新
-
- 正常提交情况: 在浏览器中输入:http://127.0.0.1:8084/purchase/commit
使用 @GlobalTransactional 注解设置分布式事务
将seata引入我们自己的项目
- 1、给每个库创建undo_log表
-
CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
- 2、引入依赖
- pom.xml
-
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-seata</artifactId> <version>2.0.0.RELEASE</version> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.0.0</version> </dependency>
-
- pom.xml
- 3、添加配置文件
- registry.conf(自行选择注册中心,默认是file)
- 如果修改注册中心和配置中心,那么同时还需要修改seata conf目录下的registry.conf
-
registry { # file 、nacos 、eureka、redis、zk type = "file" nacos { serverAddr = "localhost" namespace = "public" cluster = "default" } eureka { serviceUrl = "http://localhost:1001/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6381" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } file { name = "file.conf" } } config { # file、nacos 、apollo、zk type = "file" nacos { serverAddr = "localhost" namespace = "public" cluster = "default" } apollo { app.id = "fescar-server" apollo.meta = "http://192.168.1.204:8801" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } file { name = "file.conf" } }
- 如果修改注册中心和配置中心,那么同时还需要修改seata conf目录下的registry.conf
- registry.conf(自行选择注册中心,默认是file)
- file.conf
- 将vgroup_mapping.order-service-fescar-service-group="default"中的order-service改为自己的微服务名称
-
transport { # tcp udt unix-domain-socket type = "TCP" #NIO NATIVE server = "NIO" #enable heartbeat heartbeat = true # the client batch send request enable enable-client-batch-send-request = true #thread factory for netty thread-factory { boss-thread-prefix = "NettyBoss" worker-thread-prefix = "NettyServerNIOWorker" server-executor-thread-prefix = "NettyServerBizHandler" share-boss-worker = false client-selector-thread-prefix = "NettyClientSelector" client-selector-thread-size = 1 client-worker-thread-prefix = "NettyClientWorkerThread" # netty boss thread size,will not be used for UDT boss-thread-size = 1 #auto default pin or 8 worker-thread-size = 8 } shutdown { # when destroy server, wait seconds wait = 3 } serialization = "seata" compressor = "none" } service { #transaction service group mapping vgroup_mapping.order-service-fescar-service-group="default" #only support when registry.type=file, please don't set multiple addresses default.grouplist = "127.0.0.1:8091" #degrade, current not support enableDegrade = false #disable seata disableGlobalTransaction = false } client { rm { async.commit.buffer.limit = 10000 lock { retry.internal = 10 retry.times = 30 retry.policy.branch-rollback-on-conflict = true } report.retry.count = 5 table.meta.check.enable = false report.success.enable = true } tm { commit.retry.count = 5 rollback.retry.count = 5 } undo { data.validation = true log.serialization = "jackson" log.table = "undo_log" } log { exceptionRate = 100 } support { # auto proxy the DataSource bean spring.datasource.autoproxy = false } }
- 4、修该数据源类型(可以改也可以不改,看自己需求)
- DataSourceConfig.java(这里默认用的是Druid)
-
@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DruidDataSource druidDataSource() { return new DruidDataSource(); } /** * 需要将 DataSourceProxy 设置为主数据源,否则事务无法回滚 * * @param druidDataSource The DruidDataSource * @return The default datasource */ @Primary @Bean("dataSource") public DataSource dataSource(DruidDataSource druidDataSource) { return new DataSourceProxy(druidDataSource); } }
-
- 例如改为Hikari
-
@Configuration public class DataSourceConfig { @Primary @Bean("dataSource") public DataSource dataSource(@Value("${spring.datasource.url}") String url, @Value("${spring.datasource.username}") String username, @Value("${spring.datasource.password}") String password, @Value("${spring.datasource.driver-class-name}") String driverClassName) { HikariDataSource hikariDataSource = new HikariDataSource(); hikariDataSource.setJdbcUrl(url); hikariDataSource.setUsername(username); hikariDataSource.setPassword(password); hikariDataSource.setDriverClassName(driverClassName); return new DataSourceProxy(hikariDataSource); } }
-
- DataSourceConfig.java(这里默认用的是Druid)
- 5、在需要用分布式事务的方法上面加上 @GlobalTransactional
- 6、启动微服务前别忘了启动seata