什么是Seata?
一个可用于微服务架构的高性能且易于使用的分布式事务解决方案。
微服务中的分布式事务问题
一个典型的业务场景,电商下订单,减库存,扣余额的场景;当业务量较小的时候,我们通常会使用单机服务,使用单个本地数据源,拆分成3个子模块(订单模块,库存模块,账户模块),使用DB的本地事务来保证数据的一致性。

一旦业务量上来后,系统整体的吞吐和RT相对就会变慢,势必会影响用户体验。解决办法就是对单机服务进行服务拆分,将原先的3个子模块设计成基于3个不同数据源的3个服务(订单服务,库存服务,账户服务)。各自DB的本地事务可以保证每个事务的数据一致性。但是无法保证整个业务逻辑范围的数据一致性。

Seata解决方案

什么是分布式事务?
分布式事务是由一批分支事务组成的全局事务,通常分支事务只是本地事务。

Seata 的三个基本组件
TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

Seata管理的分布式事务的生命周期
TM 要求 TC 开始新的全局事务。TC 生成代表全局事务的 XID。
XID 通过微服务的调用链传播。
RM 将本地事务注册为 XID 到 TC 的相应全局事务的分支。
TM 要求 TC 提交或回退相应的 XID 全局事务。
TC 驱动 XID 的相应全局事务下的所有分支事务,以完成分支提交或回滚。

快速集成 Seata
环境准备
MySQL DB搭建
https://github.com/seata/seata-samples/blob/master/springboot-mybatis/sql/all_in_one.sql
Seata Server启动
# 拉取镜像docker pull seataio/seata-server# 启动容器docker run --name seata-server -p 8091:8091 -e SEATA_IP=127.0.0.1 seataio/seata-server

服务介绍
| 服务 | 地址 | 说明 |
| seata-common | N/A | 服务依赖 |
| seata-storage | 127.0.0.1:8089 | 库存服务 |
| seata-business | 127.0.0.1:8086 | 主体服务 |
| seata-order | 127.0.0.1:8088 | 订单服务 |
| seata-account | 127.0.0.1:8087 | 账户服务 |
| seata-server | 127.0.0.1:8091 | Seata Server |
seata-common
Project Directory

核心 Maven Dependency
<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.4.1</version></dependency>
seata-storage
Project Directory

applicaton.properties
spring.application.name=seata-storageserver.port=8089spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false&serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=123456mybatis.mapper-locations=classpath:mapper/**/*.xmlseata.tx-service-group=my_test_tx_groupseata.service.grouplist.default=127.0.0.1:8091logging.level.io.seata=info
seata-business
project directory

application.properties
spring.application.name=seata-businessserver.port=8086seata.service.grouplist.default=127.0.0.1:8091seata.tx-service-group=my_test_tx_grouplogging.level.io.seata=info
seata-order
project directory

application.properties
spring.application.name=seata-orderserver.port=8088spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_order?useSSL=false&serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=123456mybatis.mapper-locations=classpath:mapper/**/*.xmlseata.tx-service-group=my_test_tx_groupseata.service.grouplist.default=127.0.0.1:8091logging.level.io.seata=info
seata-account
project directory

application.properties
spring.application.name=seata-accountserver.port=8087spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_account?useSSL=false&serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=123456mybatis.mapper-locations=classpath:mapper/**/*.xmlseata.tx-service-group=my_test_tx_groupseata.service.grouplist.default=127.0.0.1:8091logging.level.io.seata=info
核心业务逻辑
使用 @GlobalTransactional 注解 管理全局事务,生成全局事务XID

查看DefaultGlobalTransaction#begin可以看到生成全局事务XID的细节

使用拦截器跨服务传递全局事务XID

使用过滤器绑定全局事务XID

Test
分别启动4个服务

测试成功场景
curl -v http://localhost:8086/api/business/purchase/commit
结果返回为true,订单表新增了一条记录,库存表扣减了库存,账户表扣减了金额。

测试失败场景
curl -v http://localhost:8086/api/business/purchase/rollback
结果返回为false,账户服务会抛出异常,全局事务会回滚,订单表不会新增记录,库存表不会扣减库存,账户表不会扣减余额。

Reference
https://github.com/seata/seata
https://github.com/seata/seata-samples
http://seata.io/zh-cn/docs/overview/what-is-seata.html
https://hub.docker.com/r/seataio/seata-server
泰克风格 只讲干货 不弄玄虚




