事务(Transaction)
事务是一种机制、一个操作序列,包含了一组数据库操作命令。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。
分布式事务
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
常见的分布式事务解决方案有2PC、3PC、TCC、本地消息表、消息事务、最大努力通知等。下面记录一下使用Seata解决分布式事务。
Seata 是什么
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
主要名词:
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata使用
以当前1.5.2版本为例:
服务端安装
- 下载服务端文件,下载地址
- 上传到服务器并解压,我这里放到
/opt/seata
目录下 - 准备使用Nacos作为配置和注册中心,执行配置:
cd /opt/seata/script/config-center/nacos #切换到目录
./nacos-config-interactive.sh #执行
根据提示输入Nacos地址和用户名密码,等待完成
- 启动:
cd /opt/seata/bin
./seata-server.sh
启动后访问服务器ip:7091即可访问Seata管理页面,用户名密码默认均为Seata。如下图
同时可以在Nacos的管理界面看到已经注册了Seata服务:
至此,服务端安装配置完成。
项目中使用
模拟场景,项目中存在account服务和order服务操作数据库,另有web服务同时调用account和order。
项目结构如下:
- 数据准备
演示用,就不分库了,不同服务使用同一个数据库,在实际使用中,微服务每个服务应该对应各自的数据库。
使用Seata的AT模式,每个数据库中都要建立undo_log表记录回滚记录,SQL如下:
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
- 项目准备
项目是就是一个简单的SpringCloud分布式项目,详细的就不贴代码了,最后会有代码地址。只贴一下Seata的核心引用和配置:
pom文件增加Seata的引用
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion> <!--剔除掉老版本的Seata -->
<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.5.2</version>
</dependency>
yml配置
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: default_tx_group #这里要和nacos里配置的一致
enable-auto-data-source-proxy: true
config:
type: nacos
nacos:
namespace:
serverAddr: 10.168.1.201:8848
group: SEATA_GROUP
userName: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
server-addr: 10.168.1.201:8848
namespace:
userName: "nacos"
password: "nacos"
- 测试
测试代码如下:使用@GlobalTransactional
开启全局事务
@Override
public void noSeata() {
userAccountClient.save(userId,BigDecimal.ZERO);
System.out.println(1/0);
ordersClient.save(userId,UUID.randomUUID().toString());
}
@Override
@GlobalTransactional
public void hasSeata() {
userAccountClient.save(userId,BigDecimal.ZERO);
System.out.println(1/0);
ordersClient.save(userId,UUID.randomUUID().toString());
}
结果是调用noSeata方法,会抛异常,但account会保存数据
调用hasSeata方法,异常后account数据会回滚,这一点从日志中也能体现:
2022-07-19 23:33:26.787 INFO 10204 --- [nio-8089-exec-1] c.b.s.a.s.impl.UserAccountServiceImpl : save user account:{"balance":0,"userId":1}
Hibernate: insert into user_account (balance, create_time, update_time, user_id, version) values (?, ?, ?, ?, ?)
2022-07-19 23:33:26.990 INFO 10204 --- [nio-8089-exec-1] c.b.s.a.s.impl.UserAccountServiceImpl : saved user account:{"balance":0,"createTime":1658244806823,"id":21,"updateTime":1658244806823,"userId":1,"version":0}
2022-07-19 23:33:27.155 INFO 10204 --- [h_RMROLE_1_1_16] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:xid=10.168.1.201:8091:639797021460082695,branchId=639797021460082696,branchType=AT,resourceId=jdbc:mysql://10.168.1.201:3306/seata-demo,applicationData={"autoCommit":false,"skipCheckLock":true}
2022-07-19 23:33:27.158 INFO 10204 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 10.168.1.201:8091:639797021460082695 639797021460082696 jdbc:mysql://10.168.1.201:3306/seata-demo
2022-07-19 23:33:27.211 INFO 10204 --- [h_RMROLE_1_1_16] i.s.r.d.undo.AbstractUndoLogManager : xid 10.168.1.201:8091:639797021460082695 branch 639797021460082696, undo_log deleted with GlobalFinished
2022-07-19 23:33:27.213 INFO 10204 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
可以看到最后做了回滚。
注意:undo_log表在回滚之后会删除记录,如果要观察记录,最好在项目中打断点。
以上,Seata使用AT模式实现分布式事务完成,测试项目代码地址