一、Seata服务端搭建
1、下载服务端
官网下载:https://github.com/seata/seata/releases
下载1.5.2版本
2、创建Seata数据库
CREATE DATABASE `ry-seata` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
3、创建Seata数据表
脚本在Seata版本解压目录/script/server/db下,目前支持mysql、oracle、postgresql;
库表生成效果:
4、创建Nacos配置
1)创建seata服务配置seataServer.properties;
Data ID:seataServer.properties
Group: SEATA_GROUP
NameSpace:seata
配置格式:properties
注:应用的Group分组名称与Seata的分组名称可以不一样;
具体内容:
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=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
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=192.168.2.93:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=kryo
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.
store.mode=db
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.2.93:3306/ry-seata?rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=60000
server.recovery.asynCommittingRetryPeriod=60000
server.recovery.rollbackingRetryPeriod=60000
server.recovery.timeoutRetryPeriod=60000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
2)创建事务路由规则配置service.vgroupMapping.default_tx_group
Data ID:service.vgroupMapping.default_tx_group
Group: SEATA_GROUP
NameSpace:seata
配置格式:TEXT
内容:default
3)配置最终效果
5、修改seata/conf/application.yml文件内容:
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
# extend:
# logstash-appender:
# destination: 127.0.0.1:4560
# kafka-appender:
# bootstrap-servers: 127.0.0.1:9092
# topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 192.168.10.164:8848
namespace: seata
group: SEATA_GROUP
username: nacos
password: nacos
file-extension: yml
data-id: seataServer.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 192.168.10.164:8848
group: SEATA_GROUP
namespace: seata
cluster: default
username: nacos
password: nacos
file-extension: yml
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
6、启动Seata服务
windox环境,直接双击:seata/bin/seata-server.bat
linux环境:
sh seata-server.sh -p 8091 -h 192.168.10.164 -m db
注:务必加启动参数,特别是IP与端口
打开seata控制台:
二、Seata客户端搭建
1、客户端添加seata依赖
<!-- SpringBoot Seata -->
<!--移除alibaba-seata自带的seata-spring-boot-starter,因为自带的版本太低 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加seata-spring-boot-starter依赖,版本需要与服务端版本相对应,此版本使用1.5.2 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
2、客户端添加配置文件
注:seata客户端的命名空间与分组名称可以与seata服务端的不一样,但若客户端有多个配置文件,那它们的命名空间与分组名称是要一致的;
1)创建seata客户端的Nacos配置文件
Data ID:seata-client-test.yml
Group: DMS_TEST_GROUP
NameSpace:test
配置格式:YAML
内容:
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
serverAddr: 192.168.10.164:8848
namespace: seata
group: SEATA_GROUP
username: nacos
password: nacos
dataId: "seataServer.properties"
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
serverAddr: 192.168.10.164:8848
namespace: seata
group: SEATA_GROUP
username: nacos
password: nacos
2)创建seata客户端的多数据源(druid)配置文件
Data ID:druid_dynamic_test.yml
Group: DMS_TEST_GROUP
NameSpace:test
配置格式:YAML
内容:
spring:
datasource:
default-transaction-isolation: 2
druid:
aop-patterns: com.ruoyi.*,com.zlt.*
stat-view-servlet: #登陆账号密码
login-password: root
login-username: root
reset-enable: true
enabled: true
allow: 192.168.2.*,127.0.0.1,192.168.10.*
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: /*.js,/*.gif,/*.jpg,/*.bmp,/*.png,/*.css,/*.ico,/druid/*
filter:
wall:
enabled: false
config:
multi-statement-allow: true
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 10000
merge-sql: true
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: oracle.jdbc.driver.OracleDriver
# url: jdbc:oracle:thin:@192.168.2.101:1521/zlt
# username: rycloud
# password: rycloud
dynamic:
druid:
# 下面为连接池的补充设置,应用到上面所有数据源中
initial-size: 5
min-idle: 5
maxActive: 20
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置获取连接等待超时的时间
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,防止sql注入
#filters: stat,slf4j
#connectionProperties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000
datasource:
# 主库数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@192.168.2.101:1521/zlt
username: ll_ems
password: ll_ems
# 审批数据源
activiti:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@192.168.2.101:1521/zlt
username: ll_ems
password: ll_ems
# 消息数据源
message:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@192.168.2.101:1521/zlt
username: rycloud
password: rycloud
seata: true
# 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭
3)每个应用服务的yml文件,增补seata开启信息
eg.
Data ID:dms-mt-biz-test.yml
Group: DMS_TEST_GROUP
NameSpace:test
配置格式:YAML
增补内容:
# seata配置
seata:
# 默认关闭,如需启用spring.datasource.dynami.seata需要同时开启
enabled: true
# Seata 应用编号,默认为 ${spring.application.name}
application-id: ${spring.application.name}
# 关闭自动代理
enableAutoDataSourceProxy: false
当然,上述1、2、3的yml文件也可以合并为一个。若有细分的话,应在服务的bootstrap.yml启动文件中将上述共享配置纳入
如:
4)每个应用服务都要创建UNDO_LOG表
MYSQL脚本:
CREATE TABLE
undo_log
(
id bigint NOT NULL AUTO_INCREMENT,
branch_id bigint NOT NULL,
xid VARCHAR(100) NOT NULL,
context VARCHAR(128) NOT NULL,
rollback_info LONGBLOB NOT NULL,
log_status INT NOT NULL,
log_created DATETIME NOT NULL,
log_modified DATETIME NOT NULL,
ext VARCHAR(100),
PRIMARY KEY (id),
CONSTRAINT ux_undo_log UNIQUE (xid, branch_id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE=utf8_general_ci;
ORACLE脚本:
CREATE TABLE
UNDO_LOG
(
ID NUMBER(19) NOT NULL,
BRANCH_ID NUMBER(19) NOT NULL,
XID VARCHAR2(100) NOT NULL,
CONTEXT VARCHAR2(128) NOT NULL,
ROLLBACK_INFO BLOB NOT NULL,
LOG_STATUS NUMBER(10) NOT NULL,
LOG_CREATED TIMESTAMP(0) NOT NULL,
LOG_MODIFIED TIMESTAMP(0) NOT NULL,
PRIMARY KEY (ID),
CONSTRAINT UX_UNDO_LOG UNIQUE (XID, BRANCH_ID)
);
COMMENT ON TABLE UNDO_LOG
IS
'AT transaction mode undo table';
5)启动应用服务
若在Seata服务端可以看到应用服务(客户端)的注册信息,则表示客户端的配置是正常的;
三、使用Seata分布式事务
1、在@Service服务里使用GlobalTransactional全局事务;
注意:一定要在Service服务里,不能在Controller,否则全局事务不会生效
参考代码:
/**
* 提交审批
* @param billVo 单据VO
* @param variables 提交审批流变量
* @param callBackFunc 提交后回调
*/
@DS("master")
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional(rollbackFor = Exception.class)
public void submitApply(T billVo, Map<String, Object> variables,
Function<List, Object> callBackFunc) throws TransactionException {
//1.提交审批
String applyUserId = variables.get(ActivitiConstants.CURRENT_LOGIN_USER_NAME) == null ? SecurityUtils.getUsername()
: String.valueOf(variables.get(ActivitiConstants.CURRENT_LOGIN_USER_NAME));
ActProcessInstance processInstance = actProcessService.submitApply(applyUserId,String.valueOf(billVo.getId()), variables);
//2.提交后回调
if (callBackFunc != null){
if (processInstance == null || StringUtils.isBlank(processInstance.getProcessInstanceId())){
//Fegin调用使用了Fallback降级或抛出的异常被全局处理
//这种情况下属于seata服务发现不了下游服务抛出的异常,导致事务不会触发回滚,需手动回滚
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
throw new CustomException("提交审批失败:审批流实例未能正常返回!");
}
List<Object> paramsList = new ArrayList<>();
paramsList.add(billVo);
paramsList.add(processInstance.getProcessInstanceId());
callBackFunc.apply(paramsList);
}
}
import com.baomidou.dynamic.datasource.annotation.DS;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.GlobalTransactionContext;
2、分支事务Transactional,使用REQUIRES_NEW
/**
* 事务传播特性设置为 REQUIRES_NEW 开启新的事务 重要!!!!一定要使用REQUIRES_NEW
*/
@DS("activiti")
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void complete(String taskId, String instanceId, Map<String, Object> variables) {
//审批动作
}
3、调试注意事项
1)尽量不在要服务间设置断点,会引起超时;
2)启动全局事务后,在控制台就可以看到全局事务信息;
3)分支事务执行后,每个服务的业务操作在undo_log会自动记录,但很快会回滚消失,所以有时不要以为没有生成undo_log日志;
四、注意事项
1、若是Oracle版本,建议引入ojdbc6驱动,减少出现莫名的问题
<!-- Seata分布式事务需要的版本 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>${oracle6.version}</version>
</dependency>
<oracle6.version>12.1.0.1-atlassian-hosted</oracle6.version>
2、在事务过程中,不要自行拦截异常,否则seata会捕获不到;
3、若Fegin调用使用了Fallback降级或抛出的异常被全局处理;
这种情况下属于seata服务发现不了下游服务抛出的异常,导致事务不会触发回滚
解决办法:
通过 GlobalTransactionContext.reload(RootContext.getXID()).rollback() 进行手动回滚;
if (processInstance == null || StringUtils.isBlank(processInstance.getProcessInstanceId())){
//Fegin调用使用了Fallback降级或抛出的异常被全局处理
//这种情况下属于seata服务发现不了下游服务抛出的异常,导致事务不会触发回滚,需手动回滚
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
throw new CustomException("提交审批失败:审批流实例未能正常返回!");
}
4、所有参与分布式事务的库表,必须要有主键,且必须是唯一的主键,不能联合主键,否则会出现: get table meta error:Failed to fetch schema of 表名。
5、Seata不支持的库表字段类型:NVarchar2
6、日志打印XID发现没有值或不一样
若是使用feign调用,则需要引入seata-spring-boot-starter
7、出现:Response[ TransactionException[Could not register branch into global session xid = xxx.xxx.xx.xx:xx
原因:Seata的AT模型调用其他服务时是异步的。seata的全局事务超时时间设置太短了,导致注册分支事务的时候,全局事务都已经进入第二阶段了。将配置文件中的事务超时等待设置长些即可:如图(如果60秒不够用可以在设置大些,但是对应的代码中全局事务超时(@GlobalTransactional(timeoutMills = 默认60秒))也要设置大些)
8、支持日期类型,使用kyro序列化
在seataServer.properties中,修改logSerialization
client.undo.dataValidation=true
client.undo.logSerialization=kryo
client.undo.onlyCareUpdateColumns=true
在应用服务的pom文件中,引入kyro相关依赖
<dependency>
<groupId>com.esotericsoftware.kryo</groupId>
<artifactId>kryo</artifactId>
<version>${kryo.version}</version>
</dependency>
<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
<version>${kryo-serializers.version}</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>${kryo-software.version}</version>
</dependency>
<kryo.version>2.24.0</kryo.version>
<kryo-serializers.version>0.45</kryo-serializers.version>
<kryo-software.version>4.0.2</kryo-software.version>