关于替换Seata 事务 1.5.2 框架实现事项
引序(简单介绍一下)
Seata 提供了两种主要的事务模式:AT(Automatic Transaction)模式和 TCC(Try-Confirm-Cancel)模式。它们有一些差异,选择哪种模式取决于你的具体需求和业务场景。
- AT 模式(自动事务模式):
- 特点: AT 模式是一种基于数据库的分布式事务模式,它依赖数据库的支持来保证全局事务的一致性。
- 工作原理: 在分布式事务中,AT 模式通过在参与者服务中对数据库操作进行undo和redo的日志记录,以实现全局事务的一致性。在全局提交阶段,各参与者服务执行事务的提交或回滚操作。
- 适用场景: 适用于业务场景相对简单、对性能要求较高的情况,且数据库支持XA协议。
- TCC 模式(Try-Confirm-Cancel 模式):
- 特点: TCC 模式是一种更加灵活的分布式事务模式,它将一个业务操作拆分为三个阶段:Try、Confirm、Cancel,允许开发者手动编写业务逻辑来处理每个阶段。
- 工作原理: 在 TCC 模式中,每个业务操作被分解为三个步骤:Try(尝试执行业务)、Confirm(确认执行业务)、Cancel(取消执行业务)。这三个步骤由开发者编写具体的业务逻辑来完成。
- 适用场景: 适用于业务场景复杂、对业务逻辑的控制要求高的情况,但需要开发者编写更多的业务逻辑代码。
选择模式的依据:
- 业务复杂度: 如果业务逻辑较为简单,不需要手动编写事务的 Try、Confirm、Cancel 逻辑,AT 模式可能更为适用。而对于复杂的业务逻辑,TCC 模式更具灵活性。
- 性能需求: AT 模式通常对性能要求较高,因为它依赖数据库的支持来完成事务操作。如果对性能有较高要求,AT 模式可能更为适用。
- 业务一致性: TCC 模式对业务的一致性有更好的控制,开发者可以更精细地处理各个阶段的业务逻辑。如果业务一致性要求高,TCC 模式可能更合适。
在实际项目中,根据具体的业务需求和系统特点进行选择。有些项目可能会选择混合使用,根据具体场景选择不同的事务模式。 Seata 还在不断演进,未来可能会有更多的事务模式和功能,可以根据项目的发展和需求进行选择。
背景是因为当前项目使用的springboot版本实在是太低了,多次尝试之后就只能兼容seata 的 1.4.2 和 1.5.2 版本了,这两个版本的服务端sql脚本有所区别大家请注意。
区别:
seata 1.4.2 使用 registry.conf 和 file.conf 部署配置文件
seata 1.5.2 使用 yml 部署配置文件
seata 的各版本部署可以直接 docker 很简单傻瓜式这里就不说了,seata服务端配置文件也和客户端进来一致就行 有 yml 或者 file.conf 两种姿势。我这里就讲yml的姿势。
1、初始化数据库
Seata 服务端配置3张配置表(必选)
Seata 客户端配置1张日志表(必选)
------------------------------------------服务端sql-------------------------
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(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(200),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-------------客户端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 = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
--有一个小坑 lock_table 表的 table_name 字段长度官方默认32位,我们业务表挺长的,我就改成了 200长度,需要的可以自己再调整。
2、导入依赖
<!-- dependencys 中新加 seata相关 start -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>1.5.1.RELEASE</version>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.5.2</version>
</dependency>
<!-- 这里想用客户端配置yml必须要引入 seata-spring-boot-starter 否则忽略 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- seata相关 end -->
<!--解决jackson报错问题 start -->
<dependency>
<groupId>com.esotericsoftware.kryo</groupId>
<artifactId>kryo</artifactId>
<version>2.24.0</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
<version>0.44</version>
</dependency>
<!--解决jackson报错问题 end -->
<!-- build 中 非必选 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<include>*.conf</include>
</includes>
<excludes>
<exclude>**/*.yml</exclude>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
</excludes>
</resource>
</resources>
3、实现数据源代理
package ***;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
* @author: xll
* @Date:
* @Description:
*/
@Configuration
public class DataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Primary
@Bean("dataSource")
public DataSourceProxy dataSource(DataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
}
注:
可能出现的问题:dataSource 数据源出现循环依赖,原因是因为seata需要代理数据源和原来的数据源有冲突,解决方法是手动注入数据源bean ,在启动类上关闭数据源的自动注入,通过以上 DataSourceConfiguration 手动注入
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
4、使用分布式事务注解
@GlobalTransactional
注意:
1、name 如果在同一次调用中的多个微服务中使用不同的 name 参数,会导致这些微服务的操作不属于同一个全局事务。这样会破坏了分布式事务的一致性,可能引发数据不一致的问题。
2、timeoutMills 超时时间 60000(60 秒)
如果多个服务都设置了 timeoutMills 参数,则 Seata 会选择其中设置值最小的超时时间作为全局事务的超时时间。
如果有的服务没有设置 timeoutMills 参数,则 Seata 会选择设置了该参数的服务中设置值最小的超时时间作为全局事务的超时时间。
5、Seata 配置文件
配置文件有两种配置姿势,按照官方的文档来实现将seata的客户端配置保存在项目下的 registry.conf 和 file.conf 文件中且进来保持和服务端配置一样。
6、客户端使用yml版本
配置客户端yml ,不再需要 registry.conf 和 file.conf
spring:
cloud:
alibaba:
seata:
tx-service-group: "tx-service-group"
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: "seata-service"
access-key:
secret-key:
enable-auto-data-source-proxy: true
data-source-proxy-mode: AT
server:
undo:
log-serialization: kryo
store:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
## 服务端数据库地址
url: ""
user: ""
password: ""
min-conn: 5
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 100
max-wait: 5000
client:
tm:
commit-retry-count: 5
rollback-retry-count: 5
default-global-transaction-timeout: 600000
degrade-check: false #关闭熔断
degrade-check-period: 2000
degrade-check-allow-times: 10
interceptor-order: -2147482648 #Ordered.HIGHEST_PRECEDENCE + 1000
undo:
data-validation: true
log-serialization: kryo
log-table: undo_log
only-care-update-columns: true
compress:
enable: true
type: zip
threshold: 64k
load-balance:
type: XID
virtual-nodes: 10
service:
vgroup-mapping:
tx-service-group: "seata-service"
enable-degrade: false
disable-global-transaction: false
disableGlobalTransaction: false
transport:
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
server-executor-thread-prefix: NettyServerBizHandler
share-boss-worker: false
client-selector-thread-prefix: NettyClientSelector
client-selector-thread-size: 1
client-worker-thread-prefix: NettyClientWorkerThread
worker-thread-size: default
boss-thread-size: 1
type: TCP
server: NIO
heartbeat: true
serialization: seata
compressor: none
enable-tm-client-batch-send-request: false
enable-rm-client-batch-send-request: true
rpc-rm-request-timeout: 15000
rpc-tm-request-timeout: 30000
config:
type: file
custom:
name:
registry:
type: eureka
eureka:
weight: 1
service-url: http://localhost:1234/eureka
application: "seata-service"
log:
exception-rate: 100
注:tx-service-group名称直接对应seata.service.vgroup-mapping名;
seata.service.vgroup-mapping.tx-service-group 直接对应 registry.conf配置文件中 eureka.application名称。
Seata XID 事务传播的源码:
SeataFeignClient.execute 重写了feign调用,然后把xid放到requestHeader里面传递给下游服务
SeataHandlerInterceptorConfiguration 实现了 WebMvcConfigurerAdapter 的 preHandle 在里面的消息头获取到xid
关于服务自动降级策略的具体实现介绍:
首先通过读取client.tm.degradeCheck是否为true,决定是否开启自检线程.随后读取degradeCheckAllowTimes和degradeCheckPeriod,确认阈值与自检周期.
假设degradeCheckAllowTimes=10,degradeCheckPeriod=2000
那么每2秒钟会进行一个begin,commit的测试,如果失败,则记录连续失败数,如果成功则清空连续失败数.连续错误由用户接口及自检线程进行累计,直到连续失败次数达到用户的阈值,则关闭Seata分布式事务,避免用户自身业务长时间不可用.
反之,假如当前分布式事务关闭,那么自检线程继续按照2秒一次的自检,直到连续成功数达到用户设置的阈值,那么Seata分布式事务将恢复使用