Seata-分布式事务框架

Seata的架构

Seata事务管理中有3个重要角色:

  • TC(Transaction Coordinator)-事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM(Transaction Manager)-事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM(Resource Manager)-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

Seata提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入

  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式,性能好,通过加全局锁保证数据一致性

  • TCC模式:最终一致的分阶段事务模式,有业务侵入,性能更好,不需要加锁,通过人工写代码实现回滚

  • SAGA模式:长事务模式,有业务侵入(无隔离,会出现脏写,这里不讲解)
    微服务可以不同的模式混用

部署Seata的TC服务

配置

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

分布式项目集成Seata

使用不同的的事务模式,有不同的流程,具体看下面的具体的事务模式

  • 引入依赖
<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>
  • 配置application.yml,让微服务通过注册中心找到seata-tc-server:
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的分布式事务的模式,每个设计的微服务都要添加
seata:
	data-source-proxy-mode: XA/TCC/AT/SAGA #开启数据源代理的XX模式
  • 给发起全局事务的入口方法添加@GlobalTransactional注解,只需要在全局事务的入口处添加,其他微服务方法不用
@override
@GlobalTransactional
public Long create(Order order){
	//创建订单
	orderMapper.insert(order);
	//扣除余额...
	//扣减库存...
	return order.getId();
}

各事务模式

AT模式

  • TM先向TC注册开启全局事务
  • 然后调用各分支
  • 各分支RM向TC注册分支事务
  • RM拦截sql执行并备份快照undo log(更新前快照和更新后快照)
  • 分支执行sql并提交
  • 提交后RM向TC报告分支事务状态
  • 若全部成功则删除快照,若有分支事务失败,则根据快照恢复提交前的数据
    Seata-分布式事务框架_第1张图片

AT模式高并发下的脏写问题

出现原因:

事务1获得DB锁后,创建快照然后修改数据100为90,提交事务并释放锁,
此时事务2获得DB锁也来修改同一个数据90为20,然后提交事务并释放锁。
此时当事务1事务状态存在失败,需要根据快照恢复时,直接就将事务2的操作给覆盖掉了,又恢复到了100。
Seata-分布式事务框架_第2张图片

解决方案:

使用全局锁(只对seata管理的全局事务生效),全局锁是由TC记录的,当事务1执行修改sql后会去获得全局锁,然后提交事务并释放DB锁。此时事务2获得DB锁修改同一数据,后去尝试获得全局锁,由于事务1没有释放全局锁此时事务2会重复尝试获取全局锁30次(默认),间隔10ms,获取失败后回滚事务并释放DB锁。
此时事务1需要恢复数据,获得事务2释放的DB锁,然后恢复数据为修改前。
Seata-分布式事务框架_第3张图片

全局锁只能用于由Seata管理的全局事务,当全局事务1更新数据后,没有释放全局锁,但此时一个普通事务修改了同一数据。
由于AT模式在创建快照时,会创建更新前和更新后两份,当执行数据恢复时会比对更新的快照与当前数据是否一致,若一致则恢复,若不一致,则记录异常,发出警告,人工介入。
Seata-分布式事务框架_第4张图片

实现方案

需要在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;

XA模式

与AT模式不同的是:

  • 在各分支执行业务sql后并不立即提交,而是等待所有的分支事务都执行完
  • RM向TC报告事务状态
  • TM向TC通知提交/回滚
  • TC检查所有分支事务状态
  • 若全部成功则通知所有RM提交,若有失败则通知回滚

Seata-分布式事务框架_第5张图片

TCC模式

TCC模式与AT模式一样是最终一致模式,性能更好,因为不需要加全局锁,通过实现Try(资源检查和预留),Confirm(业务执行与提交),Cancel(预留资源的释放)三个方法来保证数据一致性。

  • 先判断资源够不够用 Try方法
  • 如果够,则冻结需要用的资源 Try方法
  • 事务全部执行成功则扣除被冻结的资源 Confirm方法
  • 有失败则恢复被冻结的资源 Cancel方法

若期间有其他事务操作同一数据,在检查资源的时候不会计算被冻结的资源,只计算未被冻结的资源。
不依赖数据库事务,意味着即便是Redis也可以使用
Confirm和Cancel方法可能会失败,失败后Seata会重试,需要做好幂等处理

Seata-分布式事务框架_第6张图片

TCC模式的空回滚问题

存在分布式事务注册后业务没有被执行,被阻塞超时了,TC通知回滚时,该事务还没有执行Try,就要执行Cancel了,就是空回滚。
Seata-分布式事务框架_第7张图片
当该事务被执行空回滚后,业务突然不被阻塞接下去执行了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;

Seata-分布式事务框架_第8张图片

Try、Confirm、Cancel的实现

@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);
}

Seata的高可用

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

你可能感兴趣的:(Seata,分布式,Seata)