涉及到业务库都要新建:undo_log表
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) 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 = utf8 COMMENT ='AT transaction mode undo table';
依赖 ,目前和
Seata Server
服务端保持版本一致:1.3.0
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-seataartifactId>
<version>2.2.0.RELEASEversion>
<exclusions>
<exclusion>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
exclusion>
<exclusion>
<groupId>io.seatagroupId>
<artifactId>seata-allartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
<version>1.3.0version>
dependency>
application
配置
seata:
# 开启Springboot自动装配 默认true
enabled: true
# 开启数据源自动代理 默认true
enable-auto-data-source-proxy: true
# app id
application-id: ${
spring.application.name}
# 事务组(可以每个应用独立取名,也可以使用相同的名字,注意跟配置文件保持一致)
tx-service-group: ${
spring.application.name}
# 配置中心
config:
type: apollo
apollo:
app-id: ${
spring.application.name}
#apollo-meta: http://10.0.17.92:9083
apollo-meta: ${
apollo.meta}
namespace: seata
# 注册中心
registry:
type: eureka
eureka:
weight: 1
application: seata-server
#service-url: http://10.0.17.92:9001/eureka/
# eureka.service.ip-address 通过配置注入
service-url: ${
eureka.service.ip-address}
Apollo
seata
命名空间配置
# transport配置
transport.type = TCP
transport.server = NIO
transport.heartbeat = true
# 客户端事务消息请求合并发送 默认true
transport.enableClientBatchSendRequest = true
transport.compressor = none
transport.shutdown.wait = 3
transport.serialization = seata
transport.threadFactory.bossThreadPrefix = NettyBoss
transport.threadFactory.workerThreadPrefix = NettyServerNIOWorker
transport.threadFactory.serverExecutorThread-prefix = NettyServerBizHandler
transport.threadFactory.shareBossWorker = false
transport.threadFactory.clientSelectorThreadPrefix = NettyClientSelector
transport.threadFactory.clientSelectorThreadSize = 1
transport.threadFactory.clientWorkerThreadPrefix = NettyClientWorkerThread
transport.threadFactory.bossThreadSize = 1
transport.threadFactory.workerThreadSize = default
# client配置
# 异步提交缓存队列长度(默认10000)
client.rm.asyncCommitBufferLimit = 10000
# 一阶段结果上报TC重试次数(默认5)
client.rm.reportRetryCount = 5
# 自动刷新缓存中的表结构(默认false)
client.rm.tableMetaCheckEnable = false
# 是否上报一阶段成功(默认false),true用于保持分支事务生命周期完整
client.rm.reportSuccessEnable = false
# 校验或占用全局锁重试间隔(默认10ms)
client.rm.lock.retryInterval = 10
# 校验或占用全局锁重试次数(默认30)
client.rm.lock.retryTimes = 30
# 分支事务与其它全局事务回滚事务冲突时策略(优先释放本地锁让回滚)
client.rm.lock.retryPolicyBranchRollbackOnConflict = true
# 一阶段全局回滚上报TC重试次数(默认1次,建议大于1)
client.tm.rollbackRetryCount = 3
# 一阶段全局提交上报TC重试次数(默认1次,建议大于1)
client.tm.commitRetryCount = 3
# undo序列化方式(默认jackson)
client.undo.logSerialization = jackson
# 自定义undo_log表明(默认undo_log)
client.undo.logTable = undo_log
# 二阶段回滚镜像校验(默认true开启)
client.undo.dataValidation = false
# 日志异常输出概率(默认100)
client.log.exceptionRate = 100
# service配置
# seata-server服务端地址 仅在store.mode=file时生效
service.default.grouplist = 10.0.17.92:8091
# 各自项目需配置自己的 service.vgroupMapping.事务组 , seata-server为服务端事务组
service.vgroupMapping.claim-workflow = seata-server
# 开启降级,业务连续出错则不使用事务。默认false
service.enableDegrade = false
# 禁用全局事务(默认false)
service.disableGlobalTransaction = false
Seata
1.3.0版本,已经实现自动数据代理注入。
- 参数:seata.enable-auto-data-source-proxy=true ,默认为true
Seata
1.3.0版本后,不需要手动注入数据源(即以下代码)。
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
}
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DataSourceProxyConfig {
//@Value("${mybatis-plus.mapper-locations}")
//private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
//@Primary
//@Bean
//public DataSourceProxy dataSourceProxy(DataSource dataSource) {
// return new DataSourceProxy(dataSource);
//}
//代理mybaits-plus
//@Bean
//public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSourceProxy) throws Exception {
// MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
// sqlSessionFactoryBean.setDataSource(dataSourceProxy);
// sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
// sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
// return sqlSessionFactoryBean;
//}
//代理mybatis / mybatis-config
//@Bean
//public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
// SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// sqlSessionFactoryBean.setDataSource(dataSourceProxy);
// sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
// sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
// return sqlSessionFactoryBean.getObject();
//}
}
@GlobalTransactional
可以自定义名称和超时时间,默认超时时间:60秒
@GlobalTransactional(timeoutMills = 60000,rollbackFor = Exception.class)
public Result submitById(ReportDispatchingCommand command, UserLoginDTO userLoginDTO){
//业务逻辑
}
先插入
后修改插入数据
的流程,要保证所有的改库动作
都加@GlobalTransactional
注解像这样,
test2
的接口加了@GlobalTransactional
,test3
的接口没有加@GlobalTransactional
如果执行顺序是:test2
执行更新 ->test3
执行更新 ->test2
回滚
这样就会由于test3
执行了更新,导致回滚test
的时候,seata
认为有脏数据,就不回滚了。
要避免这种情况就需要给test3
的方法也加上@GlobalTransactional
的注解
// service A
@GetMapping("test2")
@GlobalTransactional
public void test2() {
bService.doInsertAndUpdate();
}
// service A
// 注意这里没有加@GlobalTransactional
@GetMapping("test3")
public void test3() {
bService.doInsertAndUpdate();
}
pom.xml
版本冲突问题
spring-cloud-alibaba-seata
中自动依赖了seata-spring-boot-starter
。但是版本对应不上,所以要处理版本不兼容问题
Could not register branch into global session
异常
- 异步调用不不加入全局事务,异步改为MQ调用
这里很重要
异常:Could not register branch into global session xid = status = Rollbacked(还有 Rollbacking、AsyncCommitting等等二阶段状态) while expecting Begin
描述:分支事务注册时,全局事务状态需是一阶段状态begin,非begin不允许注册。
属于seata框架层面正常的处理,用户可以从自身业务层面解决。
出现场景(可继续补充)
1. 分支事务是异步,全局事务无法感知它的执行进度,全局事务已进入二阶段,该异步分支才来注册
2. 服务a rpc 服务b超时(dubbo、feign等默认1秒超时),a上抛异常给tm,tm通知tc回滚,但是b还是收到了请求 (网络延迟或rpc框架重试),然后去tc注册时发现全局事务已在回滚
3. tc感知全局事务超时(@GlobalTransactional(timeoutMills = 默认60秒)),主动变更状态并通知各分支事务 回滚,此时有新的分支事务来注册
# 设置RPC调用的超时时间
ribbon:
# 连接超时
ConnectTimeout: 5000
# 响应超时 设置超时时间设置长一点。应为回滚需要时间
ReadTimeout: 10000
@GlobalTransactional
全局事务不生效事务是基于接口或基于类的代理被创建的。事务注解,没有加在方法入口,事务无法生效
@ Service
public class Service1 {
@ Autowired
private Service1 self;
public void m1() {
// 类内部直接调用,不会触发AOP
//this.m2();
//通过维护一个自己的bean来调用,从而使事务生效
self.m2();
}
@ GlobalTransactional
publiv void m2() {
}
}
Seata
后,spring
本地事务注解@Transactional
无效
- 是否所有sql都走
DataSourceProxy
代理;而DataSourceProxy
代理是直接commit代码;所以只能使用@GlobalTransactional
才能执行undo 回滚- 所以Seata就不支持 spring
@Transactional
的本地事务了? 待验证 。。。
Spring事务特性
1. @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
2. @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
3. @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
4. @Transactional(isolation = Isolation.SERIALIZABLE):串行化
1. TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
2. TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
3. TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
5. TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
6. TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
7. TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行; 如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
Seata框架设计
Seata框架的主要角色
(Transaction ID)XID 全局唯一事务ID
(Transaction Manager) TM 事务管理器
(Resource Manager ) RM 资源管理器
(Transaction Coordinator) TC 事务协调器
事务管理器:核心模块为全局事务管理器,主要负责:全局事务的开启、提交、回滚。
资源管理器:执行具体的分支事务,由对应的执行器去执行。包括重做日志,SQL的执行;
实际的SQL由连接代理ConnectionProxy执行,连接代理ConnectProxy会注册事务分支到事务 协调器TC,执行完实际SQL后,上报分支事务的状态给事务协调器TC。
事务协调器:根据事务管理器TM的全局事务的开启、提交、回滚请求,创建全局事务,驱动分支事务的提交 和回滚,并维护全局事务和分支事务的状态。
- 全局事务: 通过XID(全局唯一事务ID) 来标识
xid = 10.0.18.202:8905:138030304412573696
- 分支事务:通过分支事务ID和资源ID来标识,每个分支事务都有XID来标识属于哪个全局事务
branchId = 138030306228707329
Register branch successfully, xid = 10.0.18.202:8905:138030304412573696, branchId = 138030306228707329, resourceId = jdbc:mysql://10.0.18.200:3306/claim_auprocess ,lockKeys = c_au_image_detail_info:340489328012288
[2m2021-05-18 13:23:09.581[0;39m [32m INFO[0;39m [2m---[0;39m [2m[LoggerPrint_1_1][0;39m [36mi.s.c.r.p.server.BatchLogHandler [0;39m [2m:[0;39m SeataMergeMessage xid=10.0.18.202:8905:138030304412573696,branchType=AT,resourceId=jdbc:mysql://10.0.18.200:3306/claim_auprocess,lockKey=c_au_image_detail_info:340489328012289
Seata-client
与 Seata-server
是如何通信的1. seata-servaer 向注册中心注册
2. seata-client 向注册中心注册
3. seata-client 通过注册中心返回的seata-server服务列表,找到seata-server
1. file
文件类型:用于做概念验证,目的是通过配置文件,让client快速找到seata-server,从而免去依赖第三方注册中心
2. 非file类型
nacos
apollo
zookeeper
redis
etcd
....
DataSource
一切的核心都在
io.seata.rm.datasource.DataSourceProxy
从 0.9 版本后,已经实现自动数据代理注入,默认开启。
```yaml
seata:
开启Springboot自动装配 默认true
enabled: true
开启数据源自动代理 默认true
enable-auto-data-source-proxy: true
```
我们只要把DataSourceProxy注册成默认的 java.sql.DataSource实现,并提供给其它框架(mybatis\jdbctemplate)装配,就达到目的。所以client端的一切配置都围绕着把DataSourceProxy注册成默认的 java.sql.DataSource,或者把数据库访问框架的Datasource配置成DataSourceProxy来进行的。
LockManager
锁管理器的顶级接口:
LockManager
, 锁管理器必须实现该接口。Lock_key格式 :数据库资源^^表名^^主键ID
示例:
jdbc:mysql://10.0.17.xxx:3306/xxxx^^^act_ge_bytearray^^^341269355654272
spring cloud seata 参数配置
Seata解析-TC端file.conf文件各配置作用总结
被调用方存在两个本地事务,在调用方抛异常时,被调用方重复出现Branch Rollbacked faild
压力测试一段时间后,tm端rpc调用server端超时 RPC timeout:RPC timeout
Seata源码解析
微服务分布式事务解决方案-springboot整合分布式seata1.3.0
分布式事务两阶段提交 Eureka+Seata方案
springcloud-nacos-seata 实现分布式事务
Could not commit JDBC transaction; nested exception is io.seata.rm.datasource.exec.LockConflictException: get global lock fail
- 怀疑是竞争锁导致的。所以修改seata服务的参数
- client.rm.lock.retryInterval 20 校验或占用全局锁重试间隔 默认10,单位毫秒
- client.rm.lock.retryTimes 60 校验或占用全局锁重试次数 默认30
seata 提交全局事务失败 Unable to commit against JDBC Connection
又见分布式事务之阿里开源Seata
seata 分布式事务不生效