springcloudAlibaba_seata 介绍与安装
分布式事务问题
用例
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
- 仓储服务:对给定的商品扣除仓储数量。
- 订单服务:根据采购需求创建订单。
- 帐户服务:从用户帐户中扣除余额。
架构图
问题
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局数据一致性问题是无法保证的。
一句话,一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
Seata是什么
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各部门业务进行了有力的支撑
。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。
下面介绍Seata在分布式处理过程中的ID+三组件模型
术语
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
seata服务器扮演tc的角色
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
事务的发起方,也就是标了@GlobalTransactional注解的方法所在服务扮演着tm的角色
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
RM和数据源一一对应
处理过程
- TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;XID 在微服务调用链路的上下文中传播;
- RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
- TM 向 TC 发起针对 XID 的全局提交或回滚决议;
- TC 调度 XID 下管辖的全局分支事务,完成分支提交或回滚请求。
Seata特色功能
AT模式
AT模式是使用seata最常用的分布式事务模式,下面简单介绍一下,官网说明地址
前提
- 基于支持本地 ACID 事务的关系型数据库。
- Java 应用,通过 JDBC 访问数据库。
整体机制
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
写隔离
- 一阶段本地事务提交前,需要确保先拿到 全局锁 。
- 拿不到 全局锁 ,不能提交本地事务。
- 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
以一个示例来说明:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。
读隔离
在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。
出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。
示例
以一个示例来说明整个 AT 分支的工作过程。
业务表:product
Field | Type | Key |
---|---|---|
id | bigint(20) | PRI |
name | varchar(100) | |
since | varchar(100) |
AT 分支事务的业务逻辑:
|
|
一阶段
过程:Seata会拦截“业务SQL”
- 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = ‘TXC’)等相关的信息。
- 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
|
|
得到前镜像:
id | name | since |
---|---|---|
1 | TXC | 2014 |
-
执行业务 SQL:更新这条记录的 name 为 ‘GTS’。
-
查询后镜像:根据前镜像的结果,通过 主键 定位数据。
|
|
得到后镜像:
id | name | since |
---|---|---|
1 | GTS | 2014 |
- 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到
UNDO_LOG
表中。
|
|
-
提交前,向 TC 注册分支:申请
product
表中,主键值等于 1 的记录的 全局锁 。 -
本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
-
将本地事务提交的结果上报给 TC。
二阶段-回滚
- 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
- 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
- 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
- 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
|
|
- 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段-提交
- 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
Seata安装
官方说明
环境说明
- mysql:5.7
- nacos:1.2.0,强烈建议第二梯队,开始使用了最新版1.4.0,最新版访问这个http://localhost:8848/nacos/v1/ns/instance一直报server is DOWN now, please try again later!`,导致seata启动不了,降成1.2就可以了。
- seata:1.3.0
seata/script介绍
- client
- client客户端参数配置,我们采用AT模式,此处选择db方式(at/db文件夹)
- 存放client端sql脚本,参数配置
- at/db/mysql.sql,undo_log建表语句
- spring目录,整合spring,会用到spring的相关配置,里面是
.yml
,.properties
相关配置信息
- config-center
- 配置中心参数,本文选用nacos,选择nacos文件夹,详细配置在config.txt文件夹下
- 各个配置中心参数导入脚本以及推送脚本,config.txt(包含server和client,原名为nacos-config.txt)为通用参数文件
- nacos目录 选用nacos,里面是将配置信息批量发送到nacos的脚本
- config.txt 配置在nacos中的数据
- server
- server端参数配置,同样选用db方式(db文件夹)
- server端数据库脚本及各个容器配置
- mysql.sql:里面是seata server端global_table、branch_table、lock_table三张表的建表语句
安装
先从github仓库地址克隆一份下来,要使用到其中的一些配置、脚本
-
建数据库(seata)
-
进入脚本目录
seata/script/server/db/mysql.sql
, 执行sql语句1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
-- -------------------------------- The script used when storeMode is 'db' -------------------------------- -- the table to store GlobalSession data 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;
-
Server端参数配置
我们进入到软件目录/conf (~/soft/seata/conf),修改
registry.conf
这个文件,因为本文使用nacos配置中心存储配置,如果你使用文件存储配置还需要修改file.conf文件。修改1:registry->type:nacos,下面的nacos配置信息采用默认即可,根据你的nacos配置相应进行修改
修改2:registry->config->type:改成nacos,本文使用nacos配置中心来存储数据库等服务器配置信息,同样下面的nacos配置根据你自己的情况进行修改,如果没有定制过直接默认即可。
-
设置服务器配置信息
进入本地 资源目录
seata/script/config-center/config.txt
,展示的是 Seata 1.2.0 版本所有配置中心的内容,全部配置点击链接查看。本文使用db方式,故选择db相关配置,需要用到的配置如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
service.vgroupMapping.my_test_tx_group=default service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false store.mode=db store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true store.db.user=username store.db.password=password store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000
修改之前cp一份config.txt留作以后参考
如果你的nacos和seata都采用默认配置的话,只需要修改存储方式store.mode(db),数据库配置即可
-
推送配置信息到配置中心
进入到
nacos-config.sh
脚本所在目录,执行1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
$ sh nacos-config.sh set nacosAddr=localhost:8848 set group=SEATA_GROUP Set service.vgroupMapping.my_test_tx_group=default successfully Set service.default.grouplist=127.0.0.1:8091 successfully Set service.enableDegrade=false successfully Set service.disableGlobalTransaction=false successfully Set store.mode=db successfully Set store.db.datasource=druid successfully Set store.db.dbType=mysql successfully Set store.db.driverClassName=com.mysql.jdbc.Driver successfully Set store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true successfully Set store.db.user=username successfully Set store.db.password=password successfully Set store.db.minConn=5 successfully Set store.db.maxConn=30 successfully Set store.db.globalTable=global_table successfully Set store.db.branchTable=branch_table successfully Set store.db.queryLimit=100 successfully Set store.db.lockTable=lock_table successfully Set store.db.maxWait=5000 successfully ========================================================================= Complete initialization parameters, total-count:18 , failure-count:0 ========================================================================= Init nacos config finished, please start seata-server.
你自己打开
nacos-config.sh
脚本 看看它查找config.txt
的逻辑就可以了,只要能够读取到 config.txt 文件即可。nacos-config.sh
脚本支持传入四个参数
:-h
nacos 所在服务器的IP地址,默认为localhost
-p
nacos 端口号,默认为8848
-g
nacos 配置所属 group 名称,默认为SEATA_GROUP
-t
将 nacos 配置保存到指定的命名空间,默认为 "",代表 public 命名空间
(注意:-t
参数值接收的是命名空间ID
,不是命名空间名称
)
成功后查看nacos配置
说明配置信息已经成功配置到nacos中了,接下来就是启动seate-server
-
进入到执行目录(
/Users/david/soft/seata/bin
)下,执行1 2 3
$ ./seata-server.sh ... 2020-11-17 01:19:31.538 INFO [main]io.seata.core.rpc.netty.RpcServerBootstrap.start:155 -Server started ...
查看seata服务是否注册到配置中心,查看nacos服务管理中的服务列表
至此,Seata Server端成功启动