分布式事务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依赖

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


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

全局事务超时设置.png

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>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容