1.SeaTa简介
SeaTa 是阿里开源的可供商用的分布式事务框架 前身Fescar , java程序
1.1亮点
- 应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
- 将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚;
- 通过全局锁实现了写隔离与读隔离
- 多种事务模式 : AT、TCC、SAGA 事务模式
1.2 SeaTa相关概念
- TC :事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
- TM:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
- RM:控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
- TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。
1.3执行流程
- TM 向 TC 申请开启一个全局事务,TC 创建全局事务后返回全局唯一的 XID,XID 会在全局事务的上下文中传播;
- RM 向 TC 注册分支事务,该分支事务归属于拥有相同 XID 的全局事务;
- TM 向 TC 发起全局提交或回滚;
- TC 调度 XID 下的分支事务完成提交或者回滚
1.4 详细参数
https://seata.io/zh-cn/docs/user/configurations.html
1.5性能损耗
- 一条Update的SQL,则需要全局事务xid获取(与TC通讯)
- before image(解析SQL,查询一次数据库)
- after image(查询一次数据库)
- insert undo log(写一次数据库)
- before commit(与TC通讯,判断锁冲突)
这些操作都需要一次远程通讯RPC,而且是同步的。另外undo log写入时blob字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间(二阶段虽然是异步的,但其实也会占用系统资源,网络、线程、数据库)
2.准备工作
1.server端工作
1.TC (Seate-server ) 下载
- https://github.com/seata/seata/releases
- 官方钉钉群(群号:23171167,1群5000人已满,2群),qq群(群号:254657148)群文件共享下载
2.建表
- TC需要新建三张表
- db_store.sql
- 各个表对应功能
- 全局事务---global_table
- 分支事务---branch_table
- 全局锁-----lock_table
3.修改配置文件
- seata/conf/file.conf server服务 的日志记录方式,数据库连接信息
## transaction log store, only used in seata-server
store {
## store mode: file、db 事务日志存储模式
mode = "db"
# 服务端配置
service {
# 分组名称 需要和client端一致 chuangqi-steata
vgroup_mapping.chuangqi-steata = "chuangqi-steata"
chuangqi-steata.grouplist = "127.0.0.1:8091"
# 降级开关 默认关闭
enableDegrade = false
disable = false
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://120.26.233.25:6789/seata"
user = "root"
password = "Test@DB123"
minConn = 1
maxConn = 10
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
- seata/conf/registry.conf 不同注册中心和配置中心 file 单机本地版
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
4.启动
seata/bin/seata-server.sh
nohup sh seata-server.sh -h xx.xx.xx.xx -p 8091 -m db -n 1 -e test &
-h: 注册到注册中心的ip
-p: Server rpc 监听端口
-m: 全局事务会话信息存储模式,file、db,优先读取启动参数
-n: Server node,多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突
-e: 多环境配置参考 http://seata.io/en-us/docs/ops/multi-configuration-isolation.html
2.client端工作
2.1项目添加依赖 (单选)
- 依赖seata-all 手动配置较多
- 依赖seata-spring-boot-starter,支持yml配置
- 依赖spring-cloud-alibaba-seata,内部集成了seata,并实现了xid传递
- client 版本与 server端版本一致
2.2项目新建undo_log表
2.3 增加配置文件 (1.1.0版本)
- yml文件
seata:
enabled: true
application-id: account-api # 项目标识
tx-service-group: chuangqi-steat # seata分组名称
enable-auto-data-source-proxy: true # 开启数据源自动代理
use-jdk-proxy: false # 使用的代理方式
client:
rm:
async-commit-buffer-limit: 1000
report-retry-count: 5
table-meta-check-enable: false
report-success-enable: false
lock:
retry-interval: 10
retry-times: 30
retry-policy-branch-rollback-on-conflict: true
tm:
commit-retry-count: 5
rollback-retry-count: 5
undo:
data-validation: true
log-serialization: jackson
log-table: undo_log
log:
exceptionRate: 100
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 127.0.0.1:8091
#enable-degrade: false
#disable-global-transaction: 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-client-batch-send-request: true
config:
type: file
consul:
server-addr: 127.0.0.1:8500
apollo:
apollo-meta: http://192.168.1.204:8801
app-id: seata-server
namespace: application
etcd3:
server-addr: http://localhost:2379
nacos:
namespace:
serverAddr: localhost
group: SEATA_GROUP
zk:
server-addr: 127.0.0.1:2181
session-timeout: 6000
connect-timeout: 2000
username: ""
password: ""
registry:
type: file
consul:
cluster: default
server-addr: 127.0.0.1:8500
etcd3:
cluster: default
serverAddr: http://localhost:2379
eureka:
application: default
weight: 1
service-url: http://localhost:8761/eureka
nacos:
cluster: default
server-addr: localhost
namespace:
redis:
server-addr: localhost:6379
db: 0
password:
cluster: default
timeout: 0
sofa:
server-addr: 127.0.0.1:9603
application: default
region: DEFAULT_ZONE
datacenter: DefaultDataCenter
cluster: default
group: SEATA_GROUP
addressWaitTime: 3000
zk:
cluster: default
server-addr: 127.0.0.1:2181
session-timeout: 6000
connect-timeout: 2000
username: ""
password: ""
2.4 开启数据源代理
- 关闭spring自动代理
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
- 注入数据源
@Bean
@Autowired
public SqlSessionFactory sqlsessionfactory(HikariDataSource dataSource, Configuration configuration) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setPlugins(new Interceptor[]{new PageInterceptor()});
sqlSessionFactoryBean.setConfiguration(configuration);
return sqlSessionFactoryBean.getObject();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariDataSource dataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Primary
@Bean("dataSource")
public DataSourceProxy dataSource(DataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
2.4 初始化GlobalTransactionScanner
- 手动
public GlobalTransactionScanner globalTransactionScanner() {
String applicationName = this.applicationContext.getEnvironment().getProperty("spring.application.name");
String txServiceGroup = this.seataProperties.getTxServiceGroup();
if (StringUtils.isEmpty(txServiceGroup)) {
txServiceGroup = applicationName + "-fescar-service-group";
this.seataProperties.setTxServiceGroup(txServiceGroup);
}
return new GlobalTransactionScanner(applicationName, txServiceGroup);
}
- 自动,引入seata-spring-boot-starter、spring-cloud-alibaba-seata等jar
2.5根据项目架构 配置 XID 传递
- 手动 参考源码integration文件夹下的各种rpc实现 module
- 自动 springCloud用户可以引入spring-cloud-alibaba-seata,内部已经实现xid传递
2.6 使用
- 1.@GlobalTransaction 全局事务注解 项目中要实现分布式事务的接口加入
- 2.@GlobalLock 防止脏读和脏写,又不想纳入全局事务管理时使用。(不需要rpc和xid传递等成本)
3.注意事项
1.server端
- file模式为单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高;
- db模式为高可用模式,全局事务会话信息通过db共享,相应性能差些
- file模式可直接启动 , 无需多余配置
2.client端
- restTemplate RPC调用时 : SeataFilter ,SeataRestTemplateAutoConfiguration 需要交给spring管理 , 扫描包路径记得添加
4.各个模式介绍
1.AT模式
1.0简介
- 简单上手
- 二阶段提交
1.1 前提
- 基于支持本地 ACID 事务的关系型数据库。
- Java 应用,通过 JDBC 访问数据库
1.2实现
- 二阶段提交演变 , 整体基于本地事务 + 全局锁 + 本地锁 实现 回滚通过undo_log进行反向补偿
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
1.3 步骤
- 一阶段开启事务时 首先获取本地锁 然后开始走业务逻辑
- 本地锁获取失败 不断重试
- 一阶段提交前 , 首先尝试获取该条记录的全局锁
- 获取全局锁失败 不能提交本地事务 , 重试获取全局锁 (获取 默认 10ms一次 , 重试30次)
- client.rm.lock.retryInterval 校验或占用全局锁重试间隔 默认10 单位ms
- client.rm.lock.retryTimes 校验或占用全局锁重试次数 默认30
- 重试后仍未获取到全局锁 , 回滚本地事务 , 释放本地锁
- 获取成功 一阶段事务执行
- 获取全局锁失败 不能提交本地事务 , 重试获取全局锁 (获取 默认 10ms一次 , 重试30次)
- 二阶段事务执行
- commit : 直接执行
- rollback : 首先重新获取 该条记录的本地锁 , 然后执行反向补偿操作 实现回滚
1.4 脏写,脏读问题
1.写隔离
- 上述案例, 在tx1 二阶段提交之前 tx2可以获取到tx1还未全局提交的数据 ,tx2提交事务会脏写
- 解决 :
- tx1二阶段提交前一直拥有全局锁 , 回滚时需要获取本地锁 ,
- tx2 拥有本地锁 , 一阶段提交前需要获取到全局锁
- tx1回滚获取本地锁会不断重试 , 那么tx2获取全局锁会超时
- tx2超时 -> 回滚本地事务 , 释放本地锁
- tx1获取到本地锁 -> 事务回滚
2.读隔离
- 上述案例 , tx2是否可以读到 tx1还未全局提交的数据
- 在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
- 通过 SELECT FOR UPDATE实现
- SELECT FOR UPDATE 会去申请全局锁
- 获取失败 则重试获取 直到全局锁获取到
- 对性能消耗较大
- @GlobalLock 注解解决脏读幻读问题 (不生成undo_log)
1.5工作原理
# 分支事务的sql
update product set name = 'GTS' where name = 'TXC';
一阶段
- 解析sql , 通过条件获取到查询前镜像数据
- select id, name, since from product where name = 'TXC'; 获取到查询前镜像
- 执行sql
- 获取执行后镜像 , 根据查询前镜像结果 , 通过主键 定位查询后镜像
- select id, name, since from product where id = 1`;
- 把镜像前后数据 以及业务sql等信息 组成一条 回滚记录 插入到undo_log中
{
"branchId": 641789253,
"undoItems": [{
"afterImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "GTS"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"beforeImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "TXC"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"sqlType": "UPDATE"
}],
"xid": "xid:xxx"
}
- 提交前,向 TC 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁 。
- 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
- 将本地事务提交的结果上报给 TC。
二阶段-回滚
- 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
- 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
- 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理
- 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set name = 'TXC' where id = 1;
- 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段-提交
- 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
1.6特点
- 改造成本低 , cloud项目基本只需要添加配置文件 , 新增注解
- 普通springboot项目 - 添加配置文件 , 实现XID传递
- 隔离性
2.TCC模式
2.0简介
- 二阶段提交
2.1前提
- 每个分支事务需要具备
- 一阶段 prepare 方法 (本地提交)
- 二阶段 commit 或 rollback 方法
- TCC模式不依赖底层数据源的事务支持
- 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
- 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
- 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
- TCC其实就是 自定义本地事务 加入了全局事务的管理
2.2特点
- 性能好 , 没有多余的操作, 只有TC管理
- 隔离性
- 业务改动大 , 开发困难
3.Saga模式
3.0简介
- SEATA提供的长事务解决方案
- 业务流程中每个参与者都提交本地事务
- 当出现某一个参与者失败则补偿前面已经成功的参与者
- 一阶段正向服务 和 二阶段补偿服务都由业务开发实现
3.1前提
- 开发一阶段正向业务 和 二阶段补偿业务
3.2特点
- 适合长事务
- 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
- 一阶段提交本地事务,无锁,高性能
- 事件驱动架构,参与者可异步执行,高吞吐
- 不保证隔离性
3.3实现