Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
官方文档:https://seata.io/zh-cn/index.html
seata的几种术语:
seata目前支持多种事务模式,分别有AT、TCC、SAGA 和 XA ,文章篇幅有限,今天只讲常用的AT模式。
AT模式的特点就是对业务无入侵式,整体机制分二阶段提交(2PC)
在 AT 模式下,用户只需关注自己的业务SQL,用户的业务SQL 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
一个典型的分布式事务过程:
seata的协调者其实就是阿里开源的一个服务,我们只需要下载并且启动它。
下载地址:http://seata.io/zh-cn/blog/download.html
陈某下载的版本是1.3.0,各位最好和我版本一致,这样不会出现莫名的BUG。
下载完成后,直接解压即可。但是此时还不能直接运行,还需要做一些配置。
TC运行需要将事务的信息保存在数据库,因此需要创建一些表,找到seata-1.3.0源码的script\server\db这个目录,将会看到以下SQL文件:
陈某使用的是Mysql数据库,因此直接运行mysql.sql这个文件中的sql语句,创建的三张表如下图:
找到seata-server-1.3.0\seata\conf这个目录,其中有一个registry.conf文件,其中配置了TC的注册中心和配置中心。
默认的注册中心是file形式,实际使用中肯定不能使用,需要改成Nacos形式,改动的地方如下图:
需要改动的地方如下:
最后这份文件都会放在项目源码的根目录下,源码下载方式见文末
TC的配置中心默认使用的也是file形式,当然要是用nacos作为配置中心了。
直接修改registry.conf文件,需要改动的地方如下图:
需要改动的地方如下:
上述配置修改好之后,在TC启动的时候将会自动读取nacos的配置。
那么问题来了:TC需要存储到Nacos中的配置都哪些,如何推送过去?
在seata-1.3.0\script\config-center中有一个config.txt文件,其中就是TC所需要的全部配置。
在seata-1.3.0\script\config-center\nacos中有一个脚本nacos-config.sh则是将config.txt中的全部配置自动推送到nacos中,运行下面命令(windows可以使用git bash运行):
# -h 主机,你可以使用localhost,-p 端口号 你可以使用8848,-t 命名空间ID,-u 用户名,-p 密码
$ sh nacos-config.sh -h 127.0.0.1 -p 8080 -g SEATA_GROUP -t 7a7581ef-433d-46f3-93f9-5fdc18239c65 -u nacos -w nacos
推送成功则可以在Nacos中查询到所有的配置,如下图:
TC是需要使用数据库存储事务信息的,那么如何修改相关配置呢?
上一节的内容已经将所有的配置信息都推送到了Nacos中,TC启动时会从Nacos中读取,因此我们修改也需要在Nacos中修改。
需要修改的配置如下:
## 采用db的存储形式
store.mode=db
## druid数据源
store.db.datasource=druid
## mysql数据库
store.db.dbType=mysql
## mysql驱动
store.db.driverClassName=com.mysql.jdbc.Driver
## TC的数据库url
store.db.url=jdbc:mysql://127.0.0.1:3306/seata_server?useUnicode=true
## 用户名
store.db.user=root
## 密码
store.db.password=Nov2014
在nacos中搜索上述的配置,直接修改其中的值,比如修改store.mode,如下图:
当然Seata还支持Redis作为TC的数据库,只需要改动以下配置即可:
store.mode=redis
store.redis.host=127.0.0.1
store.redis.port=6379
store.redis.password=123456
按照上述步骤全部配置成功后,则可以启动TC,在seata-server-1.3.0\seata\bin目录下直接点击seata-server.bat(windows)运行。
启动成功后,在Nacos的服务列表中则可以看到TC已经注册进入,如下图:
至此,Seata的TC就启动完成了............
上述已经将Seata的服务端(TC)搭建完成了,下面就以电商系统为例介绍一下如何编码实现分布式事务。
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
需要了解的知识:Nacos和openFeign,有不清楚的可以看我的前两章教程,如下:
陈某整个教程使用的都是同一个聚合项目,关于Spring Cloud版本有不清楚的可以看我第一篇文章的说明。
添加依赖
新建一个seata-storage9020项目,新增依赖如下:
由于使用的springCloud Alibaba依赖版本是2.2.1.RELEASE,其中自带的seata版本是1.1.0,但是我们Seata服务端使用的版本是1.3.0,因此需要排除原有的依赖,重新添加1.3.0的依赖。
注意:seata客户端的依赖版本必须要和服务端一致。
创建数据库
创建一个数据库seata-storage,其中新建两个表:
CREATE TABLE `storage` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`num` bigint(11) NULL DEFAULT NULL COMMENT '数量',
`create_time` datetime(0) NULL DEFAULT NULL,
`price` bigint(10) NULL DEFAULT NULL COMMENT '单价,单位分',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
INSERT INTO `storage` VALUES (1, '码猿技术专栏', 1000, '2021-10-15 22:32:40', 100);
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci 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 INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;
配置seata相关配置
对于Nacos、Mysql数据源等相关信息就省略了,项目源码中都有。主要讲一下seata如何配置,详细配置如下:
spring:
application:
## 指定服务名称,在nacos中的名字
name: seata-storage
## 客户端seata的相关配置
seata:
## 是否开启seata,默认true
enabled: true
application-id: ${spring.application.name}
## seata事务组的名称,一定要和config.tx(nacos)中配置的相同
tx-service-group: ${spring.application.name}-tx-group
## 配置中心的配置
config:
## 使用类型nacos
type: nacos
## nacos作为配置中心的相关配置,需要和server在同一个注册中心下
nacos:
## 命名空间,需要server端(registry和config)、nacos配置client端(registry和config)保持一致
namespace: 7a7581ef-433d-46f3-93f9-5fdc18239c65
## 地址
server-addr: localhost:8848
## 组, 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
group: SEATA_GROUP
## 用户名和密码
username: nacos
password: nacos
registry:
type: nacos
nacos:
## 这里的名字一定要和seata服务端中的名称相同,默认是seata-server
application: seata-server
## 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
group: SEATA_GROUP
namespace: 7a7581ef-433d-46f3-93f9-5fdc18239c65
username: nacos
password: nacos
server-addr: localhost:8848
以上配置注释已经很清楚,这里着重强调以下几点:
注意:seata-storage-tx-group仅仅是后缀,要记得添加配置的时候要加上前缀service.vgroupMapping.
扣减库存的接口
逻辑很简单,这里仅仅是做了减库存的操作,代码如下:
这里的接口并没有不同,还是使用@Transactional开启了本地事务,并没有涉及到分布式事务。
到这里仓储服务搭建好了..............
搭建完了仓储服务,账户服务搭建很类似了。
添加依赖
新建一个seata-account9021服务,这里的依赖和仓储服务的依赖相同,直接复制
创建数据库
创建一个seata-account数据库,其中新建了两个表:
CREATE TABLE `account` (
`id` bigint(11) NOT NULL,
`user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户userId',
`money` bigint(11) NULL DEFAULT NULL COMMENT '余额,单位分',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
INSERT INTO `account` VALUES (1, 'abc123', 1000, '2021-10-19 17:49:53');
配置seata相关配置
Seata相关配置和仓储服务相同,只不过需要在nacos中添加一个service.vgroupMapping.seata-account-tx-group=default,如下图:
扣减余额的接口
具体逻辑自己完善,这里我直接扣减余额,代码如下:
依然没有涉及到分布式事务,还是使用@Transactional开启了本地事务,是不是很爽............
这里为了节省篇幅,陈某直接使用订单服务作为TM,下单、减库存、扣款整个流程都在订单服务中实现。
添加依赖
新建一个seata-order9022服务,这里需要添加的依赖如下:
创建数据库
新建一个seata_order数据库,其中新建两个表,如下:
CREATE TABLE `t_order` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`product_id` bigint(11) NULL DEFAULT NULL COMMENT '商品Id',
`num` bigint(11) NULL DEFAULT NULL COMMENT '数量',
`user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户唯一Id',
`create_time` datetime(0) NULL DEFAULT NULL,
`status` int(1) NULL DEFAULT NULL COMMENT '订单状态 1 未付款 2 已付款 3 已完成',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
配置和seata相关配置
Seata相关配置和仓储服务相同,只不过需要在nacos中添加一个service.vgroupMapping.seata-order-tx-group=default,如下图:
扣减库存的接口
这里需要通过openFeign调用仓储服务的接口进行扣减库存,接口如下:
以上只是简单的通过openFeign调用,更细致的配置,比如降级,自己完善.........
扣减余额的接口
这里仍然是通过openFeign调用账户服务的接口进行扣减余额,接口如下:
创建订单的接口
下订单的接口就是一个事务发起方,作为TM,需要发起一个全局事务,详细代码如下图:
有什么不同?不同之处就是使用了@GlobalTransactional而不是@Transactional。
@GlobalTransactional是Seata提供的,用于开启才能全局事务,只在TM中标注即可生效。
分别启动seata-account9021、seata-storage9020、seata-order9022,如下图:
下面调用下单接口,如下图:
从控制台输出的日志可以看出,流程未出现任何异常,事务已经提交,如下图:
果然,查看订单、余额、库存表,数据也都是正确的。
但是,这仅仅是流程没问题,并不能说明分布式事务已经配置成功了,因此需要手动造个异常。
在扣减余额的接口睡眠2秒钟,因为openFeign的超时时间默认是1秒,这样肯定是超时异常了,如下图:
此时,调用创建订单的接口,控制台日志输出如下图:
发现在扣减余额处理中超时了,导致了异常.......
此时,看下库存的数据有没有扣减,很高兴,库存没有扣减成功,说明事务已经回滚了,分布式事务成功了。
Seata客户端创建很简单,需要注意以下几点内容:
项目源码已经上传,关注公众号码猿技术专栏回复关键词9528获取!
AT模式最大的优点就是对业务代码无侵入,一切都像在写单体业务逻辑一样。
TC相关的三张表:
RM写表的过程,Seata 会拦截业务SQL,首先解析 SQL 语义,在业务数据被更新前,将其保存成before image(前置镜像),然后执行业务SQL,在业务数据更新之后,再将其保存成after image(后置镜像),最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
正常:TM执行成功,通知TC全局提交,TC此时通知所有的RM提交成功,删除UNDO_LOG回滚日志
异常:TM执行失败,通知TC全局回滚,TC此时通知所有的RM进行回滚,根据UNDO_LOG反向操作,使用before image还原业务数据,删除UNDO_LOG,但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写业务 SQL,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。
原作者:鲁班快跑
原链接:7种分布式事务方案 - 鲁班快跑 - 博客园