一、介绍
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
一个分布式事务链路需要多个系统参与, 不同的系统负责不同的角色. 一般来说, 分布式事务的参与者需要包含以下 3 个角色.
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
二、业务场景
用户进行下单操作,order-service提供创建订单接口,然后通过feign调用远程的accout-service进行账户余额扣减,再通过feign调用远程的product-service进行库存扣减操作。
在这个业务场景下,order-service、accout-service、product-service其中某一个服务发生异常就会造成分布式事务,比如fegin调用product-service时库存不足抛出了异常,那么order-service需要对订单进行回滚,account-service需要对余额进行回滚,这就会造成分布式事务的问题
三、下载最新版本及修改配置
3.1.下载seata-server
https://github.com/seata/seata/tags,这里选择v1.3.0 zip下载
3.2创建seata数据库并初始化以下3张表
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
3.3修改配置文件 conf/registry.conf文件
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "192.168.2.6:8001"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "192.168.2.6:8001"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
3.4下载config.txt 添加配置
下载地址: https://github.com/seata/seata/tree/develop/script/config-center
放置在/conf目录下,修改文件如下内容(后续初始化至nacos后,可在nacos中修改)
service.vgroup_mapping.your−service−gruop=default,中间的{your-service-gruop}为自己定义的服务组名称,服务中的application.yaml文件里配置服务组名称。
工程中有3个服务,分别是order_service、account_service、product_service,所以配置如下
3.5下载执行脚本
执行脚本下载地址:https://github.com/seata/seata/tree/develop/script/config-center/nacos
这里注意一个地方,如果nacos不是默认端口8848,需要修改脚本文件
执行nacos-config.sh脚本
sh nacos-config.sh -h 192.168.2.6
-h 后面192.168.2.6为nacos地址
成功后提示init nacos config finished, please start seata-server
3.6登录nacos查看配置中心
3.7启动seata-server
./seata-server.bat
四、修改项目
4.1引入依赖
父pom
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.2.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
子pom
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
4.2添加项目配置
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: order_service_group
config:
type: nacos
nacos:
namespace:
serverAddr: 192.168.2.6:8001
group: SEATA_GROUP
userName: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
serverAddr: 192.168.2.6:8001
group: SEATA_GROUP
namespace:
userName: "nacos"
password: "nacos"
五、启动项目测试
5.1测试前各表数据
order表
accont表
product表
5.2postman调试
从参数我们可以看出test账户对P002商品下单6个,一共300元
我们的余额满足,库存不满足,会抛出RuntimeException
5.3debug验证
order表中创建了订单
feign调用account_service扣除余额undo_log生成了记录
feign调用product_service扣除库存undo_log生成了记录
因为会库存不足,会抛出RuntimeException
放掉所有断点查看表数据是否有回滚
order表
accont表
product表
通过查看数据发现订单数据没有入库、库存、余额没有扣除,说明当出现异常时,数据有回滚,分布式事务有起作用
六、AT模式工作机制
以一个示例来说明整个 AT 分支的工作过程。
业务表:product
Field | Type | Key | |
id | bigint(20) | PRI | |
name | varchar(100) | ||
since | varchar(100) |
AT 分支事务的业务逻辑:
update product set name = 'GTS' where name = 'TXC';
一阶段
过程:
1、解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
2、查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
select id, name, since from product where name = 'TXC';
得到前镜像:
id | name | since |
---|---|---|
1 | TXC | 2014 |
3、执行业务 SQL:更新这条记录的 name 为 'GTS'。
4、查询后镜像:根据前镜像的结果,通过 主键 定位数据。
select id, name, since from product where id = 1;
得到后镜像:
id | name | since |
---|---|---|
1 | GTS | 2014 |
5、插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG
表中。
{
"branchId": 641789253,
"undoItems": [{
"afterImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "GTS"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"beforeImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "TXC"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"sqlType": "UPDATE"
}],
"xid": "xid:xxx"
}
6、提交前,向 TC 注册分支:申请 product
表中,主键值等于 1 的记录的 全局锁 。
7、本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
8、将本地事务提交的结果上报给 TC。
二阶段-回滚
1、收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
2、通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
3、数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
4、根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set name = 'TXC' where id = 1;
5、提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段-提交
1、收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
2、异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。