锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

Seata聚合 AT、TCC、SAGA 、 XA事务模式打造一站式的分布式事务解决方案

时间:2022-11-02 15:30:00 35100d07no光电传感器35100d07pc光电传感器2565d07nc光电传感器

Seata

file

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 相应的内部版本在阿里经济体中一直扮演着分布式一致性中间件的角色,帮助经济体顺利度过多年的双11BU业务得到了有力的支持。经过多年的沉淀和积累,阿里云和金融云先后销售商业产品。2019.1 为创造更完善的技术生态和包容性技术成果,Seata 对外开源正式宣布,自开放以来,广受欢迎,不到一年就成为最受欢迎的分布式事务解决方案。

官方中文网:https://seata.io/zh-cn

github项目地址:https://github.com/seata/seata

4.1 Seata术语

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务状态,推动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务范围:开始全局事务,提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源TC谈话以注册分支事务和报告分支事务的状态进行,并推动分支事务提交或回滚。

Seata 致力于提供高性能、简单易用的分布式服务。Seata 为用户提供 AT、TCC、SAGA 和 XA 为用户创建一站式分布式解决方案。

4.1 Seata AT模式

Seata 为用户提供 AT、TCC、SAGA 和 XA 为用户创建一站式分布式解决方案的事务模式。AT该模型最受欢迎,使用也很简单,但其内在原理并不简单。

AT请参相关信息请参考官方文件说明:https://seata.io/zh-cn/docs/overview/what-is-seata.html

下图是AT模式执行流程:

4.1.1 AT模型及工作流程

见官方文件:https://seata.io/zh-cn/docs/overview/what-is-seata.html

4.1.2 Seata-Server安装

我们正在选择使用Seata在版本中,您可以首先参考官方版本的匹配(Seata版本也可据自己的要求选择版本):

https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明

Spring Cloud Alibaba Version Sentinel Version Nacos Version RocketMQ Version Dubbo Version Seata Version
2.2.5.RELEASE 1.8.0 1.4.1 4.4.0 2.7.8 1.3.0
2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE 1.8.0 1.3.3 4.4.0 2.7.8 1.3.0
2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE 1.7.1 1.2.1 4.4.0 2.7.6 1.2.0
2.2.0.RELEASE 1.7.1 1.1.4 4.4.0 2.7.4.1 1.0.0
2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE 1.7.0 1.1.4 4.4.0 2.7.3 0.9.0
2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE 1.6.3 1.1.1 4.4.0 2.7.3 0.7.1

我们当前SpringCloud Alibaba的版本是2.2.5.RELEASE,对应Seata版本是1.3.0,所以我们先安装Seata-Server1.3.0

我们直接基于docker启动得到:

docker run --name seata-server -p 8091:8091 -d -e SEATA_IP=192.168.200.129 -e SEATA_PORT=8091  --restart=on-failure seataio/seata-server:1.3.0 

4.1.3 集成springcloud-alibaba

接下来,我们将开始在项目中集成使用Seata的AT官方还给出了许多关于如何集成模式的分布式事务控制的例子

https://github.com/seata/seata-samples

因此,每个人都需要自己查看各种集成模式的相应性samples

按以下步骤实现集成:

1.引入依赖包spring-cloud-starter-alibaba-seata 2:配置Seata 3.创建代理数据源 4:@GlobalTransactional全局事务控制 

案例需求:

如上图所示,如果用户打车成功,需要修改司机状态,下单,记录支付日志,每个操作都调用不同的服务,如此时hailtaxi-driver但服务实施成功,hailtaxi-order在这个时候,如何实现跨服务实现跨服务回滚?这需要分布式事务。

鉴于我们的一般事务,service层层管理,所以,改造hailtaxi-order中的OrderInfoController#add

方法将业务实现放在相应的位置Service

/*** * 下单 */ /*@PostMapping public OrderInfo add(){ ///修改驾驶员信息 司机ID=1 Driver driver = driverFeign.status("3",2); //创建订单 OrderInfo orderInfo = new OrderInfo("No" ((int)(Math.rando()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver); orderInfoService.add(orderInfo); return orderInfo; }*/

@PostMapping
public OrderInfo add() { 
        
    return orderInfoService.addOrder();
}

Service实现中:

@Service
public class OrderInfoServiceImpl  implements OrderInfoService { 
        
     @Autowired
    private DriverFeign driverFeign;

    /** * 1、修改司机信息 司机ID=1 * 2、创建订单 * @return */
    @Override
    public OrderInfo addOrder() { 
        
        //修改司机信息 司机ID=1
        Driver driver = driverFeign.status("1",2);
        //创建订单
        OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver);
        int count = orderInfoMapper.add(orderInfo);
        System.out.println("====count="+count);
        return orderInfo;
    }
}    

案例实现:

0) 创建undo_log

在每个数据库中都需要创建该表:

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

1)依赖引入

我们首先在hailtaxi-driverhailtaxi-order中引入依赖:


<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-seataartifactId>
    <version>2.2.5.RELEASEversion>
dependency>

2)配置Seata

依赖引入后,我们需要在项目中配置SeataClient 端信息,关于SeataClient端配置信息,官方也给出了很多版本的模板,可以参考官方项目:

https://github.com/seata/seata/tree/1.3.0/script,如下图:

我们可以选择spring,把application.yml文件直接拷贝到工程中,文件如下:

完整文件内容见:https://github.com/seata/seata/blob/1.3.0/script/client/spring/application.yml

修改后我们在hailtaxi-driverhailtaxi-order项目中配置如下:

seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: my_seata_group
  enable-auto-data-source-proxy: true
  use-jdk-proxy: false
  excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude
  client:
    rm:
      async-commit-buffer-limit: 1000
      report-retry-count: 5
      table-meta-check-enable: false
      report-success-enable: false
      saga-branch-register-enable: false
      lock:
        retry-interval: 10
        retry-times: 30
        retry-policy-branch-rollback-on-conflict: true
    tm:
      degrade-check: false
      degrade-check-period: 2000
      degrade-check-allow-times: 10
      commit-retry-count: 5
      rollback-retry-count: 5
    undo:
      data-validation: true
      log-serialization: jackson
      log-table: undo_log
      only-care-update-columns: true
    log:
      exceptionRate: 100
  service:
    vgroup-mapping:
      my_seata_group: default
    grouplist:
      default: 192.168.200.129: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

关于配置文件内容参数比较多,我们需要掌握核心部分:

seata_transaction: default:事务分组,前面的seata_transaction可以自定义,通过事务分组很方便找到集群节点信息。
tx-service-group: seata_transaction:指定应用的事务分组,和上面定义的分组前部分保持一致。
default: 192.168.200.129:8091:服务地址,seata-server服务地址。

注意:

现在配置信息都是托管到nacos中的,所以可以直接将配置存储到nacos中

hailtaxi-order

hailtaxi-driver

3)代理数据源

通过代理数据源可以保障事务日志数据和业务数据能同步,关于代理数据源早期需要手动创建,但是随着Seata版本升级,不同版本实现方案不一样了,下面是官方的介绍:

1.1.0: seata-all取消属性配置,改由注解@EnableAutoDataSourceProxy开启,并可选择jdk proxy或者cglib proxy
1.0.0: client.support.spring.datasource.autoproxy=true
0.9.0: support.spring.datasource.autoproxy=true

我们当前的版本是1.3.0,所以我们创建代理数据源只需要在启动类上添加@EnableAutoDataSourceProxy注解即可,

hailtaxi-orderhailtaxi-driver的启动类上分别添加该注解:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = { 
        "com.itheima.driver.feign"})
@EnableAutoDataSourceProxy
public class OrderApplication { 
        
}


4)全局事务控制

打车成功创建订单是由客户发起,在hailtaxi-order中执行,并且feign调用hailtaxi-driver,所以hailtaxi-order是全局事务入口,我们在OrderInfoServiceImpl.addOrder()方法上添加@GlobalTransactional,那么此时该方法就是全局事务的入口,

@Override
@GlobalTransactional
public OrderInfo addOrder() { 
        
    //修改司机信息 司机ID=1
    Driver driver = driverFeign.status("1",2);
    //创建订单
    OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver);
    int count = orderInfoMapper.add(orderInfo);
    System.out.println("====count="+count);
    return orderInfo;
}

5)分布式事务测试

1、测试正常情况,启动测试

id=1的司机状态手动改为1,然后进行测试

2、异常测试,在hailtaxi-order的service方法中添加一个异常,

@Override
@GlobalTransactional
public OrderInfo addOrder() { 
        
    //修改司机信息 司机ID=1
    Driver driver = driverFeign.status("1",2);
    //创建订单
    OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver);
    int count = orderInfoMapper.add(orderInfo);
    System.out.println("====count="+count);
    //模拟异常
    int i = 1 / 0;
    return orderInfo;
}

测试前,将id=1的司机状态手动改为1,将订单表清空,再次测试,看状态是否被更新,订单有没有添加,以此验证分布式事务是否控制成功!

4.2 Seata TCC模式

一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

  • 一阶段 prepare 行为
  • 二阶段 commit 或 rollback 行为

根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction ModeManual (Branch) Transaction Mode.

AT 模式(参考链接 TBD)基于 支持本地 ACID 事务关系型数据库

  • 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
  • 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
  • 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

相应的,TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

TCC实现原理:

有一个 TCC 拦截器,它会封装 Confirm 和 Cancel 方法作为资源(用于后面 TC 来 commit 或 rollback 操作)
封装完,它会本地缓存到 RM (缓存的是方法的描述信息),可以简单认为是放到一个 Map 里面
当 TC 想调用的时候,就可以从 Map 里找到这个方法,用反射调用就可以了
另外,RM 不光是注册分支事务(分支事务是注册到 TC 里的 GlobalSession 中的)
它还会把刚才封装的资源里的重要属性(事务ID、归属的事务组等)以资源的形式注册到 TC 中的 RpcContext
这样,TC 就知道当前全局事务都有哪些分支事务了(这都是分支事务初始化阶段做的事情)
举个例子:RpcContext里面有资源 123,但是 GlobalSession 里只有分支事务 12
于是 TC 就知道分支事务 3 的资源已经注册进来了,但是分支事务 3 还没注册进来
这时若 TM 告诉 TC 提交或回滚,那 GlobalSession 就会通过 RpcContext 找到 1 和 2 的分支事务的位置(比如该调用哪个方法)
当 RM 收到提交或回滚后,就会通过自己的本地缓存找到对应方法,最后通过反射或其他机制去调用真正的 Confirm 或 Cancel

5 Seata注册中心

参看:https://github.com/seata/seata/tree/1.3.0/script 可以看到seata支持多种注册中心!

5.1 服务端注册中心配置

服务端注册中心(位于seata-server的registry.conf配置文件中的registry.type参数),为了实现seata-server集群高可用不会使用file类型,一般会采用第三方注册中心,例如zookeeper、redis、eureka、nacos等。
我们这里使用nacos,seata-server的registry.conf配置如下:

由于我们是基于docker启动的seata,故可以直接进入到容器内部修改配置文件/resources/registry.conf

registry {
  # file ...nacos ...eureka...redis...zk...consul...etcd3...sofa
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "192.168.200.129:8848"
    group = "SEATA_GROUP"
    namespace = "1ebba5f6-49da-40cc-950b-f75c8f7d07b3"
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

此时我们再重新启动容器,访问:http://192.168.200.129:8848/nacos 看seata是否已注册到nacos中

5.2 客户端注册中心配置

项目中,我们需要使用注册中心,添加如下配置即可(在nacos配置中心的hailtaxi-order.yamlhailtaxi-driver-dev.yaml都修改)

参看:https://github.com/seata/seata/tree/1.3.0/script

  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.200.129:8848
      group : "SEATA_GROUP"
      namespace: 1ebba5f6-49da-40cc-950b-f75c8f7d07b3
      username: "nacos"
      password: "nacos"

此时就可以注释掉配置中的default.grouplist="192.168.200.129:8091"

完整配置如下:

seata:
enabled: true
application-id: ${ 
         spring.application.name}
tx-service-group: my_seata_group
enable-auto-data-source-proxy: true
use-jdk-proxy: false
excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude
client:
rm:
async-commit-buffer-limit: 1000
report-retry-count: 5
table-meta-check-enable: false
report-success-enable: false
saga-branch-register-enable: false
lock:
  retry-interval: 10
  retry-times: 30
  retry-policy-branch-rollback-on-conflict: true
tm:
degrade-check: false
degrade-check-period: 2000
degrade-check-allow-times: 10
commit-retry-count: 5
rollback-retry-count: 5
undo:
data-validation: true
log-serialization: jackson
log-table: undo_log
only-care-update-columns: true
log:
exceptionRate: 100
service:
vgroup-mapping:
my_seata_group: default
#grouplist:
#default: 192.168.200.129: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
registry:
type: nacos
nacos:
application: seata-server
server-addr: 192.168.200.129:8848
group : "SEATA_GROUP"
namespace: 1ebba5f6-49da-40cc-950b-f75c8f7d07b3
username: "nacos"
password: "nacos"     

测试:

启动服务再次测试,查看分布式事务是否仍然能控制住!!!

6 Seata高可用

seata-server 目前使用的是一个单节点,能否抗住高并发是一个值得思考的问题。生产环境项目几乎都需要确保能扛高并发、具备高可用的能力,因此生产环境项目一般都会做集群。

上面配置也只是将注册中心换成了nacos,而且是单机版的,如果要想实现高可用,就得实现集群,集群就需要做一些动作来保证集群节点间的数据同步(会话共享)等操作

我们需要准备2个seata-server节点,并且seata-server的事务日志存储模式,共支持3种方式,

1):file【集群不可用】

2):redis

3):db

我们这里选择redis存储会话信息实现共享。

1、启动第二个seata-server节点

docker run --name seata-server-n2 -p 8092:8092 -d -e SEATA_IP=192.168.200.129 -e SEATA_PORT=8092  --restart=on-failure seataio/seata-server:1.3.0

2、进入容器修改配置文件 registry.conf,添加注册中心的配置

registry {
  # file ...nacos ...eureka...redis...zk...consul...etcd3...sofa
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "192.168.200.129:8848"
    group = "SEATA_GROUP"
    namespace = "1ebba5f6-49da-40cc-950b-f75c8f7d07b3"
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

3、修改seata-server 事务日志的存储模式,resources/file.conf 改动如下:

我们采用基于redis来存储集群每个节点的事务日志,通过docker允许一个redis

docker run --name redis6.2 --restart=on-failure -p 6379:6379 -d redis:6.2

然后修改seata-server的file.conf,修改如下:

## transaction log store, only used in seata-server
store {
  ## store mode: file...db...redis
  mode = "redis"

  ## 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)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "mysql"
    password = "mysql"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
    host = "192.128.200.129"
    port = "6379"
    password = ""
    database = "0"
    minConn = 1
    maxConn = 10
    queryLimit = 100
  }

}

如果基于DB来存储seata-server的事务日志数据,则需要创建数据库seata,表信息如下:

https://github.com/seata/seata/blob/1.3.0/script/server/db/mysql.sql

修改完后重启

注意:另一个seata-server节点也同样需要修改其存储事务日志的模式

4、再次启动服务测试,查看分布式事务是否依然能控制成功!

本文由传智教育博学谷 - 狂野架构师教研团队发布
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力
转载请注明出处!

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章