Seata分布式事务
文章目录
一、为什么要用Seata
1.问题背景
在微服务的架构下,数据不一致
2.数据不一致的原因
在微服务的环境下,由于调用链路跨越多个应用,甚至跨越多个数据源,数据的一致性在普通情况下难以保证,导致数据不一致的原因非常多,这里列举了三个最常见的原因
1.业务异常一个服务链路调用中,如果调用的过程出现业务异常,产生异常的应用独立回滚,非异常的应用数据已经持久化到数据库。
2.网络异常调用的过程中,由于网络不稳定,导致链路中断,部分应用业务执行完成,部分应用业务未被执行。
3.服务不可用若服务不可用,无法被正常调用,也会导致问题的产生
在以往如果出现数据不一致的问题,相信大多数的解决方案是这样的
1.人工补偿数据
2.定时任务检查和补偿数据
但是这两种方式的缺点也是显然意见的,一种是浪费大量的人力成本和时间,另外一种是浪费大量的系统资源去检查数据是否一致和额外的人力成本。
二、Seata简介
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata原理和设计
我们可以把一个分布式事务理解成一个包含了若干分支事务的全局事务,全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个满足ACID的本地事务。这是我们对分布式事务结构的基本认识,与 XA 是一致的。
协议分布式事务处理过程的三个组件
- Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
- Transaction Manager ™: 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
- Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
一个典型的分布式事务过程
-
TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
-
XID 在微服务调用链路的上下文中传播; RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖; TM 向 TC 发起针对
-
XID 的全局提交或回滚决议; TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
Seata可靠吗
一项新的技术出来不是可以直接运用,需要市场的检验,观察他是否被别的企业运用到生产环境,是否真的可靠
图片不是很全,可以去seata官网查看更多合作企业
Seata官网
可以看出很多知名企业都使用Seata,让我们一起来看看seata在springcloud项目中是如何使用的。
三、springcloud+eureka+seata实现分布式事务处理
演示项目组成结构
搭建步骤
配置Seata-server
搭建Eureka
Spring Cloud教程 | 第一篇:服务的注册与发现 | Eureka
可以参照上述文章进行配置Eureka,然后对application.yml进行端口号修改
我们更改下端口号为6001
创建seata-server数据库
-- -------------------------------- 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(128),
`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;
配置seata-server
从官网下载seata-server,这里下载的是seata-server-1.4.2.zip
,下载地址:https://github.com/seata/seata/releases
解压seata-server安装包到指定目录,接着我们修改conf目录下的file.conf
配置文件,主要修改自定义事务组名称,事务日志存储模式为db及数据库连接信息;
把mode形式改为db:
之后在file.conf设置数据库等信息,主要设置数据库链接,账户名和密码
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.cj.jdbc.Driver"
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
url = "jdbc:mysql://127.0.0.1:3306/seata-server?serverTimezone=UTC"
user = "root"
password = "123456"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
然后进入conf下面的registry.conf文件,修改注册中心为eureka如图:
现在我们启动eureka-server
并启动seata-server:
双击\seata-server-1.4.2\bin\seata-server.bat
我们访问Eureka服务http://localhost:6001
能看到seata-server已经注册到eureka:
配置搭建分布式事务
其实就是不同的Client,具体如何搭建可参考Spring Cloud教程 | 第一篇:服务的注册与发现 | Eureka:四、创建Client
Maven依赖
pom.xml添加如下:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
</dependency>
application.properties
配置文件中添加如下
logging.level.io.seata=info
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
在了解怎么配置之后
我们下载并打开演示用的例子https://gitee.com/lin_qichun/seata.git
springCloud-eureka-seata文件
并修改account,order,storage,bussiness里application.properties文件,file.conf,registry.conf文件
application.properties文件:
spring.application.name=storage-service
server.port=8081
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.cloud.alibaba.seata.tx-service-group=my_test_tx_group
logging.level.org.springframework.cloud.alibaba.seata.web=debug
logging.level.io.seata=info
eureka.instance.hostname=127.0.0.1
eureka.instance.prefer-ip-address=true
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:6001/eureka/
feign.hystrix.enabled=true
spring.main.allow-bean-definition-overriding=true
以及file.conf文件,service里边配置的事务组应该跟服务名保持一致
修改file.conf文件中greouplist为seata-server服务器地址,如图:
修改registry.conf
在BusinessService里需要使用分布事务处理的地方加上 @GlobalTransactional注解,如:
实例演示
启动account,order,storage,bussiness这四个项目
在Navicat里连接远程数据库:
rm-2zetd9474ydd1g5955o.mysql.rds.aliyuncs.com
用户:workshop
密码:Workshop123
打开fescar
我们分别点开前三个表,可以观察到:
账户表U100000用户有10000
订单表则没有数据
库存表库存为100
我们使用postman发送一条请求http://localhost:8084/purchase/commit
得到全局事务提交反馈则表明成功
我们再去观察三个表:
账户表:钱少了3000
订单表:新增了一条记录
库存表:少了30库存
这时我们把库存改为比30小,或者钱数改为比3000小来模仿库存不足或者钱数不足的情况,我们这里用修改钱数举例
递交数据请求:
可看到执行回滚,观察3个表,数据都无改变
现在来模拟程序出错,看seata是否执行回滚
我们在AccountService里加上两行错误代码,账户程序在执行到此处时会报错,重启AccountApplication,同时我们将数据库里的钱修改大于3000,调用链接
可以看到报错,此时我们去观察数据库那3个表,发现数据没变,表明回滚成功