Seata事务管理中有3个重要角色:
Seata提供了四种不同的分布式事务解决方案:
XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式,性能好,通过加全局锁保证数据一致性
TCC模式:最终一致的分阶段事务模式,有业务侵入,性能更好,不需要加锁,通过人工写代码实现回滚
SAGA模式:长事务模式,有业务侵入(无隔离,会出现脏写,这里不讲解)
微服务可以不同的模式混用!
seata/conf/registry.conf
type = "注册中心类型(nacos,zk,eureka)"
#以nacos为例,将其他注册中心内容删除
registry{
nacos{
application = "seata-tc-server"
serverAddr = "nacosIP:8848"
group = "需要协调的微服务所在nacos组(例:DEFAULT_GROUP)"
namespace = ""
cluster = "SH"
username = "nacos"
password = "nacos"
}
}
config{
type = "nacos" #配置文件使用nacos配置
nacos {
serverAddr = "nacosIP:8848"
group = "SEATA_GROUP"
namespace = ""
username = "nacos"
password = "nacos"
dataId = "seataServer.yaml" # 配置文件的文件名
}
}
Nacos中配置列表添加seataServer.yaml配置文件:
store:
mode: db #全局事务/分支事务数据存储方式,db代表数据库
db:
datasource: druid
dbType: mysql
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql//127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
user: root
password: 123456
minConn: 5
maxConn: 30
globalable: global_table
branchTable: branch_table
queryLimit: 100
lockTable: lock_table
maxWait: 5000
# 事务、日志等配置
server:
recovery:
committingRetryPeriod: 1000
asynCommittingRetryPeriod: 1000
rollbackingRetryPeriod: 1000
timeoutRetryPeriod: 1000
maxCommitRetryTimeout: -1
maxRollbackRetryTimeout: -1
rollbackRetryTimeoutUnlockEnable: false
undo:
logSaveDays: 7
logDelatePeriod: 86400000
# 客户端与服务端传输方式
transport:
serialization: seata
compressor: none
# 关闭metrics功能,提高性能
metrics:
enabled: false
registryType: compact
exporterList: prometheus
exporterPrometheusPort: 9898
seataServer.yaml文件中的seata库下global_table与branch_table两张表:
/*
Navicat Premium Data Transfer
Source Server : local
Source Server Type : MySQL
Source Server Version : 50622
Source Host : localhost:3306
Source Schema : seata_demo
Target Server Type : MySQL
Target Server Version : 50622
File Encoding : 65001
Date: 20/06/2021 12:38:37
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for branch_table
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` tinyint(4) NULL DEFAULT NULL,
`client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime(6) NULL DEFAULT NULL,
`gmt_modified` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of branch_table
-- ----------------------------
-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`timeout` int(11) NULL DEFAULT NULL,
`begin_time` bigint(20) NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`xid`) USING BTREE,
INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of global_table
-- ----------------------------
-- ----------------------------
-- Records of lock_table
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;
seata/bin/seata-server
可以在nacos的服务列表中查看是否成功 seata-tc-server
使用不同的的事务模式,有不同的流程,具体看下面的具体的事务模式
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
<exclusions>
<exclusion>
<artifactId>seata-spring-boot-starterartifactId>
<groupId>io.seatagroupId>
exclusion>
exclusions>
dependency>
<dependency>
<artifactId>seata-spring-boot-starterartifactId>
<groupId>io.seatagroupId>
<version>${seata.version}version>
dependency>
seata:
registry:
# TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址#参考tc服务自己的registry.conf中的配置,
# 包括:地址、namespace、group、application-name 、cluster
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-tc-server #tc服务在nacos中的服务名称
tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
service:
vgroup-mapping: #事务组与TC服务cluster的映射关系
seata-demo: SH #事务组:集群
seata:
data-source-proxy-mode: XA/TCC/AT/SAGA #开启数据源代理的XX模式
@GlobalTransactional
注解,只需要在全局事务的入口处添加,其他微服务方法不用@override
@GlobalTransactional
public Long create(Order order){
//创建订单
orderMapper.insert(order);
//扣除余额...
//扣减库存...
return order.getId();
}
事务1获得DB锁后,创建快照然后修改数据100为90,提交事务并释放锁,
此时事务2获得DB锁也来修改同一个数据90为20,然后提交事务并释放锁。
此时当事务1事务状态存在失败,需要根据快照恢复时,直接就将事务2的操作给覆盖掉了,又恢复到了100。
使用全局锁(只对seata管理的全局事务生效),全局锁是由TC记录的,当事务1执行修改sql后会去获得全局锁,然后提交事务并释放DB锁。此时事务2获得DB锁修改同一数据,后去尝试获得全局锁,由于事务1没有释放全局锁此时事务2会重复尝试获取全局锁30次(默认),间隔10ms,获取失败后回滚事务并释放DB锁。
此时事务1需要恢复数据,获得事务2释放的DB锁,然后恢复数据为修改前。
全局锁只能用于由Seata管理的全局事务,当全局事务1更新数据后,没有释放全局锁,但此时一个普通事务修改了同一数据。
由于AT模式在创建快照时,会创建更新前和更新后两份,当执行数据恢复时会比对更新的快照与当前数据是否一致,若一致则恢复,若不一致,则记录异常,发出警告,人工介入。
需要在Seata的TC服务配置文件中关联的seata数据库中创建表lock_table,用于存储全局锁:
-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
`row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`branch_id` bigint(20) NOT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`row_key`) USING BTREE,
INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
在相应的微服务的数据库中创建 undo_log表,用于存储快照:
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
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;
与AT模式不同的是:
TCC模式与AT模式一样是最终一致模式,性能更好,因为不需要加全局锁,通过实现Try(资源检查和预留),Confirm(业务执行与提交),Cancel(预留资源的释放)三个方法来保证数据一致性。
若期间有其他事务操作同一数据,在检查资源的时候不会计算被冻结的资源,只计算未被冻结的资源。
不依赖数据库事务,意味着即便是Redis也可以使用
Confirm和Cancel方法可能会失败,失败后Seata会重试,需要做好幂等处理
存在分布式事务注册后业务没有被执行,被阻塞超时了,TC通知回滚时,该事务还没有执行Try,就要执行Cancel了,就是空回滚。
当该事务被执行空回滚后,业务突然不被阻塞接下去执行了Try,但此时事务已经结束了,这就是业务悬挂。
在微服务的数据库中创建一张表记录当前的事务状态,在空回滚Cancel执行前判断是否执行过了Try,在业务悬挂Try执行前判断是否已经执行过Cancel空回滚。这张表account_freeze_tbl为:
-- ----------------------------
-- Table structure for account_freeze_tbl
-- ----------------------------
DROP TABLE IF EXISTS `account_freeze_tbl`;
CREATE TABLE `account_freeze_tbl` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户Id',
`freeze_money` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '冻结金额',
`state` int(1) NULL DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
@LocalTCC
public interface TCCService {
/**
* Try逻辑,@TwoPhaseBusinessAction中的name属性要与当前方法名一致,用于指定Try逻辑对应的方法
*/
@TwoPhaseBusinessAction(name = "prepare", commitMethod = "confirm",rollbackMethod = "cancel")
void prepare(@BusinessActionContextParameter(paramName = "param") String param);
/**
* 二阶段confirm确认方法、可以另命名,但要保证与commitMethod一致
* @param context 上下文,可以传递try方法的参数
* @return boolean 执行是否成功
*/
boolean confirm (BusinessActionContext context);
/**
* 二阶段回滚方法,要保证与rollbackMethod一致
*/
boolean cancel (BusinessActionContext context);
}
TC服务作为Seata的核心,自然要保证高可用,在不同的地区机房都要部署
微服务需要根据TC服务的所属cluster从注册中心获得TC服务
为了能不重启服务切换TC服务,需要在配置文件中将事务组与集群的映射配置放在nacos配置中心,并且服务读取seata的配置也要配置application.yml为从nacos读取client.yml。
application.yml:
#添加
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
group: SEATA_GROUP
data-id: client.yml #nacos中的配置文件名
#删除
seata:
service:
vgroup-mapping: #事务组与TC服务cluster的映射关系
seata-demo: SH #事务组:集群
client.yml:
#事务组映射关系1,配置TC服务事务组:集群
service:
vgroupMapping:
seata-demo: SH
enableDegrade: false4
disableGlobalTransaction: false
#与TC服务的通信配置
transport:
type: TCP
server: NIO
heartbeat: true
enableCientBatchSendRequest: false
shutdown:
wait: 3
threadFactory:
bossThreadPrefix: NettvBoss
workerThreadPrefix: NettyServerNIOWorker
serverExecutorThreadPrefix: NettyServerBizHandler
shareBossWorker: false
clientSelectorThreadPrefix: NettyClientSelector
bossThreadSize: 1
workerThreadSize: default
#RM配置
client:
rm:
asyncCommitBufferLimit: 10000
lock:
retryInterval: 10
retryTimes: 30
retryPolicyBranchRollbackOnConflict: true
reportRetryCount: 5
tableMetaCheckEnable: false
tableMetaCheckerInterval: 60000
sqlParserType: druid
reportSuccessEnable: false
sagaBranchRegisterEnable: false#TM配置
#TM配置
tm:
commitRetryCount: 5
rollbackRetryCount: 5
defaultGlobalTransactionTimeout: 60000
degradeCheck: false
degradeCheckAllowTimes: 10
degradeCheckPeriod: 2000
#undo日志断置
undo:
dataValidation: true
logSerialization: jackson
onlyCareUpdateColumns: true
logTable: undo_log
compress:
enable: true
type: zip
threshold: 64k
log:
exceptionRate: 100