Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
中文官方站点:https://seata.io/zh-cn/
seata github开源地址:https://github.com/seata
2 分布式事务简介
2.1 本地事务
在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。
数据库事务的四大特性:ACID
A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失败的情况。
C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏。比如:张三向李四转 100 元,转账前和转账后的数据是正确状态这叫一致性,如果出现张三转出 100 元,李四账户没有增加 100 元这就出现了数 据错误,就没有达到一致性。
I(Isolation):隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务的运行过程的中间状态。通过配置事务隔离级别可以比避免脏读、重复读问题。
D(Durability):持久性,事务完成之后,该事务对数据的更改会持久到数据库,且不会被回滚。
数据库事务在实现时会将一次事务的所有操作全部纳入到一个不可分割的执行单元,该执行单元的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。
2.2 分布式事务
随着互联网的快速发展,软件系统由原来的单体应用转变为分布式应用
分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务,例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。
3 SpringCloud Alibaba分布式基础案例搭建
我们模拟一个简单下单业务,客户端调用rest对外服务,rest服务再调用订单服务实现创建订单和账户服务实现账户扣钱操作,最终来完整下单业务;
2.1 案例架构设计
所有服务都注册到nacos中,方便feign远程调用;订单服务,账户服务各自有独立数据库;架构设计如下图:
———————————————
整体项目结构如下图:
onlinebank是父项目,主要是做一些依赖管理,依赖版本管理,管理所有子module项目;
common-service子项目,主要是引入其他子项目需要的公共依赖,以及公共实体,工具类,配置类的统一封装;
record-service子项目,主要提供订单服务,生成订单;
account-service子项目,主要提供账户服务,根据订单扣钱操作;
seata-web子项目,主要处理客户端下单请求,feign远程调用order,和account服务接口,最终完成下单处理;
2.2 数据库设计我们新建两个数据库,分别是db_order(订单数据库),db_account(账户数据库),
db_order数据库里面新建表account订单表:
DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `card_no` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `balance` int(6) DEFAULT NULL, `status` int(11) DEFAULT NULL, `user_id` int(11) DEFAULT NULL COMMENT '关联用户的id', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of account -- ---------------------------- INSERT INTO `account` VALUES (1, '1', '110', 900, 1, 1); INSERT INTO `account` VALUES (2, '2', '123456', 3700, 1, 1); INSERT INTO `account` VALUES (3, '3', '123123', 300, 0, 1); INSERT INTO `account` VALUES (4, '4', '456456', 200, 0, 1);
db_order数据库里面新建表transaction_record订单表:
DROP TABLE IF EXISTS `transaction_record`; CREATE TABLE `transaction_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `card_no` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `transaction_date` date DEFAULT NULL, `amout` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `balance` int(19) DEFAULT NULL, `transaction_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `remark` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 74 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
以上就是我的项目结构and数据库
相信各位大佬肯定不是来看我怎么从零搭建项目来的,我直接说怎么在项目里面融合seata事务!!因为我之前就是看某位大佬的博客将seata融入进去的!首先seata-web引入依赖(就是你做转账和生成订单 2个服务做中转站的服务)这只是我的依赖 重点使用seata依赖
com.changan common-service 0.0.1-SNAPSHOT com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-seata com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-loadbalancer
seata-web.yaml
server:
port: 80
servlet:
context-path: /
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: seata-web
record-service.yaml
spring:
application:
name: record-service
cloud:
nacos:
discovery:
server-addr: 服务器或本地ip:8848 #nacos的服务注册中心
alibaba:
seata:
tx-service-group: my_test_tx_group #组
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://数据库地址本机或服务器ip:3307/db_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
username: root
password: 123456
server:
port: 8082
mybatis-plus:
mapper-locations: classpath*:/mapper/*Mapper.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
type-aliases-package: com.changan.entity
management:
endpoint:
web:
exposure:
include: '*'
seata:
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 127.0.0.1:8091
enable-degrade: false
disable-global-transaction: false
account-service.yaml
spring:
application:
name: account-service
cloud:
nacos:
discovery:
server-addr: 同上:8848 #nacos的服务注册中心
alibaba:
seata:
tx-service-group: my_test_tx_group
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://同上:3307/db_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
username: root
password: 123456
server:
port: 8282
mybatis-plus:
mapper-locations: classpath*:/mapper/*Mapper.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
type-aliases-package: com.changan.entity
management:
endpoint:
web:
exposure:
include: '*'
seata:
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 127.0.0.1:8091
enable-degrade: false
disable-global-transaction: false
主要多了服务分组和集群分组的配置:
然后这是我的项目结构
@GlobalTransactional 分布式事务注解
目前我们配置环境和项目已经搭建好了,接下来只需要再下载seata主推的是AT模式强一致性解决方案,需要下载seata server来使用分布式事务。
下载和运行seata serverseata server地址:https://github.com/seata/seata/releases
最新版本 v1.4.2
我们用windows演示,下载seata-server-1.4.2.zip
seata-server配置文件:
seata server所有的配置都在conf文件夹内,该文件夹内有两个文件我们必须要详细介绍下。
seata server默认使用file(文件方式)进行存储事务日志、事务运行信息,我们可以通过-m db脚本参数的形式来指定,目前仅支持file、db这两种方式。
file.conf
该文件用于配置存储方式、透传事务信息的NIO等信息,默认对应registry.conf文件内的file方式配置。
registry.conf
seata server核心配置文件,可以通过该文件配置服务注册方式、配置读取方式。
注册方式目前支持file 、nacos 、eureka、redis、zk、consul、etcd3、sofa等方式,默认为file,对应读取file.conf内的注册方式信息。
读取配置信息的方式支持file、nacos 、apollo、zk、consul、etcd3等方式,默认为file,对应读取file.conf文件内的配置。
file.conf我们加一个service配置:
service {
#vgroup->rgroup 服务分组 集群分组默认为"default"
vgroupMapping.my_test_tx_group = "default"
#only support single node
default.grouplist = "127.0.0.1:8091"
#degrade current not support 服务降级达到多次错误不走seata
enableDegrade = false
#disable
disable = false
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}
这里事务分组概念,我们最后单独讲,主要是为了集群故障的时候,能快速切换,实现高可用;
配置分组映射,集群分组再配置grouplist,暴露server地址,提供给seata-client调用;
最后我们运行bin目录下的seata-server.bat启动seata-server:
每个数据库都建一个undo_log回滚日志表CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8分布式案例测试
三个子项目一起运行;
两个RM在seata-server注册成功;
都注册成功!
如果接口调用一切正常,日志清空,全局事务提交,完成本次事务操作;
启动测试!手动抛出一个异常转账和订单都回滚成功!
seata事务日志mysql持久化配置seata默认事务支持是file文件存储,不怎么好,不方便查看和管理;所以我们一般是db存储;
修改conf下的file.conf配置文件
把mode改成"db",
然后下方mysql配置改成你们对应的配置即可,数据库要自己先建立,比如我这边定义是db_seata
还有三个表,global_table,branch_table,lock_table分别是全局事务会话表,分支事务会话表,锁数据表;
建表语句获取:https://gitee.com/dhb414/seata/blob/master/script/server/db/mysql.sql#
-- -------------------------------- 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;
因为都是临时数据,最终提交事务会被清理,我们要看数据的话,还是需要服务代码里打断点,debug进行捕获;
下面这个是我捕获到的一个数据;
nacos注册中心和配置中心支持Seata支持注册服务到Nacos,以及支持Seata所有配置放到Nacos配置中心,在Nacos中统一维护;
seata-web的yaml修改一下
server:
port: 80
servlet:
context-path: /
# 集成Nacos注册中心支持
spring:
cloud:
nacos:
discovery:
server-addr: 150.158.193.42:8848
alibaba:
seata:
tx-service-group: my_test_tx_group
application:
name: seata-web
#集成Nacos配置中心支持
seata:
config:
type: nacos
nacos:
server-addr: 150.158.193.42:8848
group: SEATA_GROUP
namespace: ""
registry:
type: nacos
nacos:
application: seata-web
server-addr: 150.158.193.42:8848
group: "SEATA_GROUP"
namespace: ""
作为Nacos配置中心,我们需要把seata的一些配置上传到Nacos,配置比较多,官方给了我们一个 config.txt 我们修改后,上传到Nacos。
config.txt下载地址:https://github.com/seata/seata/blob/develop/script/config-center/config.txt
我们做一些修改:
修改好的文件,放seata目录下:
之后运行仓库中提供的nacos脚本,将信息提交到nacos控制台,如果有需要更改,可直接通过控制台更改
脚本地址:https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
我们把 nacos-config.sh 文件放到 conf 目录下
右击鼠标,选 Git Bash Here
我们执行命令
就是说我这样是不行的会报错Please check the cluster status. 然后sh nacos-config.sh -h 150.158.193.42直接这样运行我就可以
我是运行了比较多次你们也可以试试 我不知道为什么会中断重新运行就OK了
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP
注:命令解析:-h -p 指定nacos的端口地址;-g 指定配置的分组,注意,是配置的分组;-t 指定命名空间id; -u -w指定nacos的用户名和密码,同样,这里开启了nacos注册和配置认证的才需要指定。
等执行完:
我们会发现Nacos配置中心就有seata的配置了;
就成功了!
seata事务分组介绍
事务分组是什么?
事务分组是seata的资源逻辑,类似于服务实例。在file.conf中的my_test_tx_group就是一个事务分组。
通过事务分组如何找到后端集群?
首先程序中配置了事务分组(GlobalTransactionScanner 构造方法的txServiceGroup参数)
程序会通过用户配置的配置中心去寻找service.vgroupMapping .[事务分组配置项],取得配置项的值就是TC集群的名称
拿到集群名称程序通过一定的前后缀+集群名称去构造服务名,各配置中心的服务名实现不同
拿到服务名去相应的注册中心去拉取相应服务名的服务列表,获得后端真实的TC服务列表
为什么这么设计,不直接取服务名?
这里多了一层获取事务分组到映射集群的配置。这样设计后,事务分组可以作为资源的逻辑隔离单位,出现某集群故障时可以快速failover,只切换对应分组,可以把故障缩减到服务级别,但前提也是你有足够server集群。
暑期编程PK赛 得CSDN机械键盘等精美礼品!


