Seata: Simple Extensible Autonomous Transaction Architecture Seata
是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA
事务模式,为用户打造一站式的分布式解决方案。 —— 引用自 SEATA 官方文档
开源时间:2019年1月
Seata AT模式是基于二阶段提交的事务模式,优点是对原有业务无入侵性,入门简单。
在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
下载seata-server-1.1.0服务端 :
下载地址:https://github.com/seata/seata/releases/download/v1.1.0/seata-server-1.1.0.zip
下载seata-server-0.0.9服务端 :(1.1.0缺少一些配置需要从0.9版本里找)
下载地址:https://github.com/seata/seata/releases/download/v0.9.0/seata-server-0.9.0.zip
seata-server中,/conf目录下,有两个配置文件,需要结合自己的情况来修改。
本次demo中使用了Nacos作为注册、配置中心,Seata全局事务选择数据库存储方式。配置如下
registry{}中是注册中心相关配置,config{}中是配置中心相关配置。seata中,注册中心和配置中心是分开实现的。
#注册中心配置选项
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos" #使用nacos
nacos {
serverAddr = "localhost" #设置配置中心地址
namespace = "" #namespace的命名空间为空的话默认是public
cluster = "default"
}
...
}
#配置中心配置选项
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos" #使用nacos
nacos {
serverAddr = "localhost" #同上
namespace = ""
}
...
}
里面有事务组配置,锁配置,事务日志存储等相关配置信息。
## transaction log store, only used in seata-server
store {
## store mode: file、db
mode = "db"
## 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 = "druid"
## mysql/oracle/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "root"
minConn = 1
maxConn = 10
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
}
}
Seata-server v1.x release版缺少初始化配置相关文件,因此需要从v0.9版本导入。
从Seata-server v0.9版本复制 nacos.config.*(3个文件)到Seata server v1.1.0。
需要修改的配置如下:
#存储模式设置为数据库存储。
store.mode=db
#设置存储数据库相关信息。
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://172.16.103.27:3306/seata?useUnicode=true
store.db.user=root
store.db.password=root
store.db.branch.table=branch_table
store.db.datasource=dbcp
store.db.dbType=mysql
store.db.global.table=global_table
store.db.lockTable=lock_table
.....
#设置业务系统相关配置(vrgroupMapping)。
#---事务组(驼峰命名为v1.0以上版本格式。)
service.vgroupMapping.tx_tison_group=default
#添加业务系统中子事务:(3个微服务)
#---事务组中子事务storage-service
service.vgroupMapping.storage-service-fescar-service-group=default
#---事务组中子事务order-service
service.vgroupMapping.order-service-fescar-service-group=default
#---事务组中子事务account-service
service.vgroupMapping.account-service-fescar-service-group=default
......
特别注意事项:
关于nacos-config 的坑,官方更新1.0之后对配置的key做了一些升级。统一格式命名为驼峰命名,否则在启动seata服务的时候会报错,报错也会打印找不到指定的key。
例如:store.db.driver-class-name 改成 store.db.driverClassName=com.mysql.jdbc.Driver
当Server端存储模式为db时,需要创建global_table、branch_table、lock_table。
见官方文档Nacos Queck Start
当Seata设置Nacos为注册中心时,需要把Seata的配置初始化到Nacos配置中心。
方式1:脚本导入
在seata1.0的包里已经没有提供 nacos-config.txt 的配置文件及nacos-config.sh。只能用seata0.9版本,在seata\conf\nacos-config.txt、nacos-config.sh。该文件可通过nacos-config.sh脚本导入。
执行脚本的命令:
sh nacos-config.sh -h localhost -p 8848
方式2:http注册 (window系统不支持sh命令时)
可以自己写个post请求脚本或使用postMan工具,将nacos-config.txt里的所有参数注册到nacos。例如下面
请求方式:POST 请求地址:http://192.168.1.151:8848/nacos/v1/cs/configs? 封装的参数格式:
{
dataId: store.db.driverClassName
namespace:public
group:SEATA_CONFIG_GROUP
content:com.mysql.jdbc.Driver
}
启动路径在 seata\bin\seata-server.bat。对应的nacos注册中心的服务列表对应的命名空间里就会有该服务。
编写简单的项目模拟seata分布式事务 分别为:订单服务、库存服务、用户服务,核心模块。 springcloud seata+nacos+feign+mybatis-plus
core:通用依赖及Seata初始化配置工具
order:订单服务
storage:库存服务
user:用户服务
其中order-service通过FeignClient调用user-service进行扣款处理,调用storage-service进行库存减少处理。
事务组概念请见官网:事务分组
配置Seata管理分布式事务时,需要保证Server端(TC)的事务分组和子事务(RM)的事务分组保持一致。
每个微服务,都需要注册RM(资源管理器)到Seata。
#resource/registry.config
registry {
type = "nacos"
nacos {
serverAddr = "172.16.103.27:8848"
namespace = ""
cluster = "default"
}
}
config {
type = "nacos"
nacos { #需要与Seata初始化配置保持一致
serverAddr = "172.16.103.27:8848"
namespace = ""
group = "SEATA_CONFIG_GROUP"
cluster = "default"
}
}
注:v1.0版本之后,Seata支持yml/properties配置,本次demo中并未使用。
在事务链涉及的服务的数据库中新建 undo_log 表用来存储 UndoLog 信息,用于二阶段回滚操作,表中包含xid、branchId、rollback_info 等关键字段信息。
1.8 2.2.0.RELEASE 1.1.0 2.0.0.RELEASE … … com.alibaba.cloud spring-cloud-alibaba-seata ${spring-cloud-alibaba-seata.version} io.seata seata-all io.seata seata-all ${seata.version} … …
注意事项
seata-all 0.x版本和seata-all 1.x版本存在不兼容问题,spring-cloud-alibaba-seata中又包含seata-all低版本,注意排除。
seata-spring-boot-starter
1.0.0可用于替换seata-all,GlobalTransactionScanner自动初始化(依赖SpringUtils) 若其他途径实现GlobalTransactionScanner初始化,请保证io.seata.spring.boot.autoconfigure.util.SpringUtils先初始化; starter使用file配置中心时默认开启数据源自动代理
spring-cloud-alibaba-seata
2.1.0内嵌seata-all 0.7.1,2.1.1内嵌seata-all 0.9.0,2.2.0内嵌seata-spring-boot-starter 1.0.0
2.1.0和2.1.1兼容starter解决方案:
@SpringBootApplication注解内exclude掉spring-cloud-alibaba-seata内的com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration
3.5 添加全局事务
/**
* GlobalTransactional 分布式事务注解
*/
@Override
@GlobalTransactional
public void generateOrder(String userId, String commodityCode, Integer count){
//假设商品默认价格为:100元
int unitPrice = 100;
int money = count * 100;
//扣除用户的余额
DefaultResult defaultResult = userServiceClient.deduction(userId,money);
//如果状态不为200直接返回
if (defaultResult.getStatus() != 200) {
//回滚事务
throw new RuntimeException(defaultResult.getMsg());
}
//如果用户扣除成功,生成订单
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCommodityCode(commodityCode);
orderInfo.setMoney(money);
orderInfo.setCount(count);
orderInfo.setUserId(userId);
save(orderInfo);
String xid = RootContext.unbind();
//扣除库存
defaultResult = storageServiceClient.deduction(commodityCode,count);
if (defaultResult.getStatus() != 200) {
//抛出异常,回滚事务
throw new RuntimeException(defaultResult.getMsg());
}
}
全局事务发生异常时,不回滚部分事务
String xid = RootContext.unbind(); //解绑Xid
... //不需要回顾的处理。
RootContext.bind(xid); //重新绑定xid
仅支持ACIM模式的关系型数据库事务,自动回滚(基于SQL自动补偿)
需要数据库带有InnoDB引擎
官方网站支持数据库列表:MySQL、Oracle、PostgreSQL
不支持文件相关操作的回滚。
不支持非关系型数据库的回滚。
只能在JDK 8以上环境中使用(Seata限制)
附录
1.官方链接
官方:https://seata.io/zh-cn/docs/overview/terminology.html
Git:https://github.com/seata
资源目录:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
2.常见问题
1.registry.conf修改问题
registry.conf修改为application.yml后异常:
Issue: 将配置参数改为application.yml后的问题
I have searched the issues of this repository and believe that this is not a duplicate.
Ⅰ. Issue Description
尝试将registry.conf和file.conf配置到application.yml中,目前发现一下两个问题,
undo相关配置不生效(估计seata.client.rm.lock相关属性也有类似问题)
service.disable-global-transaction不生效
2.注册类型为null异常
经排查,必须在@SpringApplication注解内手动exclude掉spring-cloud-alibaba-seata内的com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration自动装配类,使用seata-spring-boot-starter内的io.seata.spring.boot.autoconfigure.SeataAutoConfiguration对GlobalTransactionScanner进行装配。否则项目运行时会优先使用GlobalTransactionAutoConfiguration进行装配,导致在启动加载阶段报错(”io.seata.common.exception.NotSupportYetException: not support register type: null“),报错位置为io.seata.spring.boot.autoconfigure.provider.SpringBootConfigurationProvider#get(String dataId),该方法在使用seata的spring工具类SpringUtils.getBean(propertyClass)从ApplicationContext中获取ConfigProperties.class的Bean时,因无法从容器中到符合要求的Bean而抛出NullPoinException异常.
Issue: 同时引入spring-cloud-alibaba-seata和seata-spring-boot-starter依赖,出现启动阶段无法读取ConfigProperties.class(SpringUtils.getBean抛出空指针异常)
Ⅰ. Issue Description
经排查,必须在@SpringApplication注解内手动exclude掉spring-cloud-alibaba-seata内的com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration自动装配类,使用seata-spring-boot-starter内的io.seata.spring.boot.autoconfigure.SeataAutoConfiguration对GlobalTransactionScanner进行装配。否则项目运行时会优先使用GlobalTransactionAutoConfiguration进行装配,导致在启动加载阶段报错(”io.seata.common.exception.NotSupportYetException: not support register type: null“),报错位置为io.seata.spring.boot.autoconfigure.provider.SpringBootConfigurationProvider#get(String dataId),该方法在使用seata的spring工具类SpringUtils.getBean(propertyClass)从ApplicationContext中获取ConfigProperties.class的Bean时,因无法从容器中到符合要求的Bean而抛出NullPoinException异常.
依赖列表:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-seataartifactId>
<version>2.1.1.RELEASEversion>
dependency>
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-allartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
...
3.无法找到服务
no available service ‘null’ found, please make sure registry config correct
Seata 初始化配置到Nacos的配置文件有问题。
查看事务组名称是否相同。
Seata服务端中配置了子事务(客户端)vgroupMapping,参考见附录-参考配置-Seata配置导入Nacos。
查看Nacos保存的配置中,命名空间和组(Group)是否一致。
DEMO源码
demo参考:https://github.com/a970066364/spring-cloud-alibaba-seata
官方Demo: https://github.com/seata/seata-samples
参考配置
Seata配置导入Nacos
store.mode=db #存储模式设置为数据库存储。
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://172.16.103.27:3306/seata?useUnicode=true
store.db.user=root
store.db.password=root
store.db.branch.table=branch_table
store.db.datasource=dbcp
store.db.dbType=mysql
store.db.global.table=global_table
store.db.lockTable=lock_table
store.db.maxConn=3
store.db.minConn=1
store.db.queryLimit=100
store.file.dir=file_store/data
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
client.async.commit.buffer.limit=10000
client.lock.retry.internal=10
client.lock.retry.times=30
client.report.retry.count=5
client.support.spring.datasource.autoproxy=true
metrics.enabled=false
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
metrics.registryType=compact
recovery.asynCommittingRetryPeriod=1000
recovery.committingRetryPeriod=1000
recovery.rollbackingRetryPeriod=1000
recovery.timeoutRetryPeriod=1000
service.disable=false
service.disableGlobalTransaction=false
service.enableDegrade=false
service.max.commit.retry.timeout=-1
service.max.rollback.retry.timeout=-1
#---事务组(驼峰命名为v1.0以上版本格式,下划线命名为v0.9以下版本格式,根据服务器版本选择。)
service.vgroupMapping.tx_tison_group=default
service.vgroup_mapping.tx_tison_group=default
#---事务组中子事务storage-service
service.vgroup_mapping.storage-service-fescar-service-group=default
service.vgroupMapping.storage-service-fescar-service-group=default
#---事务组中子事务order-service
service.vgroup_mapping.order-service-fescar-service-group=default
service.vgroupMapping.order-service-fescar-service-group=default
#---事务组中子事务account-service
service.vgroup_mapping.account-service-fescar-service-group=default
service.vgroupMapping.account-service-fescar-service-group=default
store.file.session.reload.read_size=100
transaction.undo.data.validation=true
transaction.undo.log.delete.period=86400000
transaction.undo.log.save.days=7
transaction.undo.log.serialization=jackson
transaction.undo.log.table=undo_log
transport.compressor=none
transport.heartbeat=true
transport.serialization=seata
transport.server=NIO
transport.shutdown.wait=3
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.bossThreadSize=1
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.workerThreadSize=8
transport.type=TCP
官方资源链接:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
Seata相关yaml配置
以下配置为v1.0新增,用于替代项目中(client端)registry.conf和file.conf配置文件。
社区issue中有Bug记录——将配置参数改为application.yml后的问题,建议谨慎尝试。
seata:
enabled: true
application-id: applicationName
tx-service-group: my_test_tx_group
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: ""
SQL(MySQL)
服务端
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,
`gmt_modified` DATETIME,
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(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
客户端
-- 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`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id',
`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 NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME NOT NULL COMMENT 'modify datetime',
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';