分布式事务解决方案-Seata应用实践

前言

随着微服务架构的流行,系统之间的交互变得更加复杂,事务一致性的保障成为一个挑战。Seata提供了一套完整的分布式事务解决方案,能够确保分布式系统中各个参与方的事务一致性。


一、Seata是什么?

seata官方文档icon-default.png?t=N7T8https://seata.io/zh-cn/docs/user/quickstart/

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

四大事务模式:

XA模式: XA模式是一个标准化的分布式事务处理协议,它使用两阶段提交(Two-Phase Commit,2PC)协议来确保多个资源管理器(RM)参与的分布式事务的一致性。在XA模式中,事务管理器负责协调各个参与者的资源管理器,并决定事务的提交或回滚。

AT模式: AT(Automatic Transactional)模式是Seata框架中的一种分布式事务解决方案。AT模式通过在每个参与者(服务)的数据库操作中添加适当的事务注解,使得Seata能够自动协调并管理分布式事务的提交或回滚,以保证事务的一致性。

TCC模式: TCC(Try-Confirm-Cancel)模式是一种基于补偿机制的分布式事务解决方案。在TCC模式中,事务操作被分解为三个阶段:尝试(Try)、确认(Confirm)和取消(Cancel)。参与者通过实现这三个阶段的业务逻辑,并提供相应的补偿操作,来保证分布式事务的一致性。

SAGA模式: SAGA模式是一种用于实现长事务处理的分布式事务解决方案。在SAGA模式中,事务被分解为多个连续的局部事务,每个局部事务都有相应的补偿操作。通过按照预定义的顺序执行这些局部事务和补偿操作,SAGA模式实现了分布式事务的一致性和容错性。

XAATTCCSAGA
一致性强一致弱一致弱一致最终一致
隔离性完全隔离基于全局锁隔离基于资源预留隔离无隔离
代码侵入有,需要编写3个接口有,需要编写状态机和补偿业务
性能非常好非常好
场景对一致性、隔离性有高要求的业务基于关系型数据库的大多数分布式事务场景都可以对性能要求较高的事务;有非关系型数据库要参与的事务业务流程长、业务流程多;参与者包含其它公司或遗留系统服务,无法提供TCC模式要求的三个接口

二、windows搭建

1.下载并配置

1.1、下载

seata下载icon-default.png?t=N7T8https://github.com/apache/incubator-seata/releases选择自己需要的版本下载。

1.2、解压

1.3、修改配置文件seate\seata\conf\application.yml,修改seata的config对应的nacos信息和seata的registry对应的nacos信息

配置seata的config和registry类型为nacos,并把nacos相关的配置信息完善;

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.0.74:8847
      namespace: rfid
      group: SEATA_GROUP
      username: nacos
      password: nacos
      data-id: seataServer.properties
 
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.0.74:8847
      group: SEATA_GROUP
      namespace: rfid
      cluster: default
      username: nacos
      password: nacos
 
#  store:
    # support: file 、 db 、 redis
 #   mode: file
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

1.4、nacos添加配置文件seataServer.properties,配置seata服务的相关信息,例如数据库连接等

具体配置代码给出如下:

#公共部分
transport.serialization=seata
transport.compressor=none
transport.heartbeat=true
registry.type=nacos
config.type=nacos
#server端
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
#存储模式选db
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
#这里url跟上rewriteBatchedStatements=true,原因看官网-参数配置-附录7,增加批量插入效率
store.db.url=jdbc:mysql://192.168.10.23:19131/seata?useUnicode=true$rewriteBatchedStatements=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
store.db.user=root
store.db.password=Digitalor@MCRFID1688

#默认1和20,稍微调大点
store.db.minConn=5
store.db.maxConn=30
store.db.maxWait=5000
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.lockTable=lock_table
store.db.queryLimit=100
#监控,只支持prometheus
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
#client端
seata.enabled=true
seata.enableAutoDataSourceProxy=true
seata.useJdkProxy=false
transport.enableClientBatchSendRequest=true
client.log.exceptionRate=100
# 全局事务Mysql数据的自动清理,默认为false,防止数据过大
service.disableGlobalTransaction=false
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.rm.reportSuccessEnable=false
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=false
#一阶段全局提交和回滚结果上报TC重试次数,默认1,这里改成3
client.tm.commitRetryCount=3
client.tm.rollbackRetryCount=3
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.logTable=undo_log
client.undo.onlyCareUpdateColumns=true
client.rm.sqlParserType=druid
#自定义事务组my_tx_group
service.vgroupMapping.my_tx_group=default

1.5、mysql建表

1.5.1、seata数据库四张表

global_table:全局事务信息

branch_table :事务参与者信息(分支事务)

lock_table:全局锁

distributed_lock:分布式锁

建表语句地址

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
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_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- 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(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
1.5.2、各分支数据库undo_log表
CREATE TABLE `undo_log` (
  `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(128) 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 NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
  `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';

1.6、seata启动,运行seate\seata\bin\seata-server.bat

1.7、客户端展示,登录seata client对事务信息进行查看,地址http://localhost:7091,账号密都码默认为:seata

在你的事务进行过程中,可以在client查到相关的事务信息,信息在事务结束后会被删除,是为了防止数据库无用数据过大,你也可以在配置里定义不删除。

2.Seata实践(AT模式)

2.1、项目maven包下载

我本地alibaba-cloud版本为 2.2.6.RELEASE,其对应的seata版本为1.3.0,但是想使用seata1.5.2的版本,故需要替换seata核心包

<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>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.5.2</version>
        </dependency>

2.2、本地yml(properties)配置

# seata配置
seata.enabled=true
# 启用自动数据源代理功能
seata.enable-auto-data-source-proxy=true
# Seata 应用编号,默认为 ${spring.application.name}
seata.application-id=${spring.application.name}
# Seata 事务组编号,用于 TC 集群名
seata.tx-service-group=my_tx_group
seata.config.type=nacos
seata.config.nacos.server-addr=192.168.0.74:8847
seata.config.nacos.namespace=rfid
seata.config.nacos.group=SEATA_GROUP
seata.config.nacos.username=nacos
seata.config.nacos.password=nacos
seata.config.nacos.data-id=seataServer.properties
seata.registry.type=nacos
seata.registry.nacos.application=seata-server
seata.registry.nacos.server-addr=192.168.0.74:8847
seata.registry.nacos.namespace=rfid
seata.registry.nacos.group=SEATA_GROUP
seata.registry.nacos.cluster=default
seata.registry.nacos.username=nacos	
seata.registry.nacos.password=nacos
seata.service.vgroup-mapping.my_tx_group=default

2.3、使用@GlobalTransactional处理分布式事务的方法,在项目中使用AT模式对现有代码进行无侵入式分布式事务改造

在代码发生异常即会触发回滚

@GlobalTransactional(timeoutMills = 10 * 60 * 1000, rollbackFor = Exception.class)
public Result test(){
    
}

2.4、@GlobalTransactional使用注意点

2.4.1、注意@GlobalTransaction和@Transaction的联动使用中事务不生效的问题,最好被调用方的接口不使用Transaction。
2.4.2、@GlobalTransactional在使用多线程时的xid失效问题,每个子线程都不受主线程的xid管控,需要将xid传入子线程,并给子线程重新绑定xid。

代码示例:

@GlobalTransactional(timeoutMills = 10 * 60 * 1000, rollbackFor = Exception.class)
public Result batchPut(List<Demo> list) throws InterruptedException {
        CopyOnWriteArrayList<String> modelIdMessageList = new CopyOnWriteArrayList();
        CopyOnWriteArrayList<String> exceptionList = new CopyOnWriteArrayList();

        // 获取全局事务ID
        String xid = RootContext.getXID();

        CountDownLatch countDownLatch = new CountDownLatch(list.size());
        list.forEach(re->
                threadPoolTaskExecutor.execute(() -> {
                   
                    this.putA(
                            countDownLatch,
                            xid
                    );
                  
                })
        );

        // 等待子线程全部完成
        countDownLatch.await();

        // 消息队列补偿、redis补偿
        if (exceptionList.size() > 0) {
            ...
            throw new RuntimeException("user-defined GlobalTransactional rollback");
        }
        return Result.success();
    }



public void putA(
            CountDownLatch countDownLatch,
            String xid
    ) {
        try {
            // 绑定全局事务ID
            RootContext.bind(xid);
            ...
        } catch (Exception e) {
          
        } finally {
            // 释放全局事务ID
            RootContext.unbind();
            // 线程信号量减一
            countDownLatch.countDown();
        }
    }


三、Linux搭建

linux服务器搭建seata和本地没有太大的区别,只是需要把seata运行到服务器上就行,不管是以jar包的方式运行,还是以docker容器的方式运行。

1、以jar包的方式在服务器运行

1)、linux服务器下载seata-server-1.5.2.zip或者本地下载上传到服务器,解压zip(命令:unzip seata-server-1.5.2.zip);

2)、其他配置和本地配置一样,都改为服务器相关信息的配置即可;

3)、启动seata,运行seate\seata\bin\seata-server.sh;也可使用自定义脚本start_seata.sh启动,可直接使用也可参考优化;

cd /home/jenkins/apps/seata/bin
sh seata-server.sh -h 192.168.100.100 -p 8091

4)注意:服务器可能没定义JAVA_HOME,启动过程中可能会报错,使用命令定义

export JAVA_HOME=/path/to/java

2、以容器的方式在服务器运行

操作基本类似,只是需要下载seata对应版本镜像在容器中运行,配置相关文件;


总结

对自己在学习和应用seata AT模式的过程进行了一个梳理,也给自己加强一下印象,后续有机会的话会应用TCC模式并进行文章输出。SAGA模式也曾使用过,有机会也会整理一个使用思路出来。

  • 29
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值