分布式事务Seata(AT模式)整合Nacos

一、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;


seata库表脚本.jpg

库表生成效果:


Seata服务库表效果.png

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)配置最终效果

Seata服务端配置.png

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控制台.png

二、Seata客户端搭建

1、客户端添加seata依赖

 
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-seata
            
                
                    io.seata
                    seata-spring-boot-starter
                
            
        
        
        
            io.seata
            seata-spring-boot-starter
            1.5.2
        

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启动文件中将上述共享配置纳入
如:


Seata配置加载.png

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服务端可以看到应用服务(客户端)的注册信息,则表示客户端的配置是正常的;


客户端注册信息.png

三、使用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 variables,
                            Function 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 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 variables) {
         //审批动作
     }

3、调试注意事项

1)尽量不在要服务间设置断点,会引起超时;
2)启动全局事务后,在控制台就可以看到全局事务信息;
3)分支事务执行后,每个服务的业务操作在undo_log会自动记录,但很快会回滚消失,所以有时不要以为没有生成undo_log日志;

四、注意事项

1、若是Oracle版本,建议引入ojdbc6驱动,减少出现莫名的问题

      
            
                com.oracle
                ojdbc6
                ${oracle6.version}
            
12.1.0.1-atlassian-hosted

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秒))也要设置大些)

全局事务超时设置.png

8、支持日期类型,使用kyro序列化

在seataServer.properties中,修改logSerialization

client.undo.dataValidation=true
client.undo.logSerialization=kryo
client.undo.onlyCareUpdateColumns=true

在应用服务的pom文件中,引入kyro相关依赖

           
                com.esotericsoftware.kryo
                kryo
                ${kryo.version}
            
            
                de.javakaffee
                kryo-serializers
                ${kryo-serializers.version}
            
            
                com.esotericsoftware
                kryo
                ${kryo-software.version}
            
        2.24.0
        0.45
        4.0.2

你可能感兴趣的:(分布式事务Seata(AT模式)整合Nacos)