Spring Cloud Alibaba 微服务组件 Seata 分布式事务(九)

   Spring Cloud Aibaba 学习目录

1. Spring Cloud Alibaba 微服务介绍(一)

2. Spring Cloud Alibaba 之Nacos 安装(二)

3. Spring Cloud Alibaba 微服务组件 Nacos 注册中心(三)

4. Spring Cloud Alibaba 微服务负载均衡 Ribbon(四)

5. Spring Cloud Alibaba 微服务整合 OpenFeign(五)

6. Spring Cloud Alibaba 微服务组件 Nacos 配置中心(六)

7. Spring Cloud Alibaba 微服务组件 Sentinel 服务保护(七)

8. Spring Cloud Alibaba 分布式事务概念(八)

9. Spring Cloud Alibaba 微服务组件 Seata 分布式事务(九)

10. Spring Cloud Alibaba 服务网关 Gateway(十)

11. Spring Cloud Alibaba 微服务组件 Skywalking 分布式任务链(十一)

语雀文档:Spring Cloud Aibaba 学习 · 语雀


Seata 分布式事务概念

Seata 是什么

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和XA 事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式,阿里云上有商用版本的GTS(Global TransactionService 全局事务服务)

官网:Seata

源码: https://github.com/seata/seata

官方Demo : https://github.com/seata/seata-samples

seata版本:v1.4.0


Seata 的三大角色

在 Seata 的架构中,一共有三个角色:

TC (Transaction Coordinator) - 事务协调者

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

TM (Transaction Manager) - 事务管理器

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

RM (Resource Manager) - 资源管理器

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

其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。

在 Seata 中,一个分布式事务的生命周期如下:

执行流程

  1. TM 请求 TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。当一进入事务方法中就会生成XID ;  (global_table 就是存储的全局事务信息 )
  2.  RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联。 当运行数据库操作方法,branch_table 存储事务参与者
  3.  TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
  4.  TC驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。

设计思路

AT模式的核心是对业务无侵入,是一种改进后的两阶段提交,其设计思路如图

  • 第一阶段

业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。核心在于对业务 sql 进行解析,转换成 undolog,并同时入库,这是怎么做的呢?先抛出一个概念 DataSourceProxy 代理数据源,通过名字大家大概也能基本猜到是什么个操作,后面做具体分析

参考官方文档: Seata AT 模式

  • 第二阶段

分布式事务操作成功,则TC通知RM异步删除undolog

分布式事务操作失败,TM向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。

整体执行流程

设计亮点

相比与其它分布式事务框架,Seata 架构的亮点主要有几个:

  1. 应用层基于 SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
  2. 将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚;
  3. 通过全局锁实现了写隔离与读隔离;

存在的问题

  • 性能损耗

一条 Update 的SQL,则需要全局事务xid获取(与TC通讯)、before image(解析SQL,查询一次数据库)、after image(查询一次数据库)、insert undo log(写一次数据库)、before commit(与TC通讯,判断锁冲突),这些操作都需要一次远程通讯RPC,而且是同步的。另外 undo log 写入时 blob 字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间。

  • 性价比

为了进行自动补偿,需要对所有交易生成前后镜像并持久化,可是在实际业务场景下,这个是成功率有多高,或者说分布式事务失败需要回滚的有多少比率?按照二八原则预估,为了20%的交易回滚,需要将80%的成功交易的响应时间增加5倍,这样的代价相比于让应用开发一个补偿交易是否是值得?

  • 全局锁

热点数据

相比XA,Seata 虽然在一阶段成功后会释放数据库锁,但一阶段在commit前全局锁的判定也拉长了对数据锁的占有时间,这个开销比XA的 prepare 低多少需要根据实际业务场景进行测试。全局锁的引入实现了隔离性,但带来的问题就是阻塞,降低并发性,尤其是热点数据,这个问题会更加严重。

  • 回滚锁释放时间

Seata在回滚时,需要先删除各节点的 undo log,然后才能释放 TC 内存中的锁,所以如果第二阶段是回滚,释放锁的时间会更长。

  • 死锁问题

Seata 的引入全局锁会额外增加死锁的风险,但如果出现死锁,会不断进行重试,最后靠等待全局锁超时,这种方式并不优雅,也延长了对数据库锁的占有时间。


Seata 快速开始

Seata Server(TC)环境搭建

seata 官网文档

Seata部署指南

下载 Seata Server 安装包

https://github.com/seata/seata/releases

Server 端存储模式

  • file:(默认) 单机模式,全局事务会话信息内存中读写并持久化本地文件 root.data,性能较高(默认)
  • db :(5.7+)高可用模式,全局事务会话信息通过db共享,相应性能差些

                1. 打开config/file.conf  

                2. 修改 mode="db"

                3. 修改数据库连接信息(URL\USERNAME\PASSWORD)

                4. 新建表:可以去seata提供的资源信息中下载:

                        \script\server\db\mysql.sql

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

  ## 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:3307/seata-server"
    user = "root"
    password = "zlp123456"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }


}
  • redis:Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置适合当前场景的redis持久化配置

资源目录https://github.com/seata/seata/tree/1.3.0/script

  • client   存放client端sql脚本,参数配置
  • config-center  各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件
  • server  server端数据库脚本及各个容器配置

db 存储模式+Nacos(注册&配置中心)部署

注册中心

将Seata Server注册到Nacos,修改conf目录下的 registry.conf 配置

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

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }

  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }

  file {
    name = "file.conf"
  }
}

启动

然后启动注册中心Nacos Server

1. 进入Nacos 安装目录,启动Nacos

startup.bat‐m standalone

2. 启动seata-server 服务,进入\seata\bin目录

seata-server.bat

Nacos 注册服务列表

配置中心

config {
  # file、nacos 、apollo、zk、consul、etcd3
  #type = "file"
  type = "nacos"
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
  }
  
  file {
    name = "file.conf"
  }
}

注意:如果配置了 seata server 使用 nacos 作为配置中心,则配置信息会从nacos读取,file.conf可以不用配置。 客户端配置 registry.conf 使用 nacos 时也要注意group要和seata server中的group一致,默认group是"DEFAULT_GROUP"

获取/seata/script/config-center/config.txt,修改配置信息

配置事务分组, 要与客户端配置的事务分组一致

#my_test_tx_group 需要与客户端保持一致  default需要跟客户端和 registry.conf 中 registry 中的cluster保持一致

(客户端properties配置:spring.cloud.alibaba.seata.tx‐service‐group=my_test_tx_group)

事务分组:  异地机房停电容错机制

my_test_tx_group 可以自定义  比如:(guangzhou、shanghai...) , 对应的client也要去设置

seata.service.vgroup‐mapping.projectA=guangzhou

default   必须要等于 registry.confi  cluster = "default"

配置参数同步到Nacos

shell:

sh ${SEATAPATH}/script/config-center/nacos/nacos‐config.sh ‐h localhost ‐p 8848 ‐g SEATA_GROUP 

参数说明:

-h: host,默认值 localhost

-p: port,默认值 8848

-g: 配置分组,默认值为 'SEATA_GROUP'

-t: 命名空间信息,对应 Nacos 的命名空间ID字段, 默认值为空 ''

精简配置

service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=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
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=username
store.db.password=password
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

启动Seata Server

源码启动: 执行server模块下io.seata.server.Server.java的main方法

命令启动: 

bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 -e test

集群模式启动Seata Server

bin/seata‐server.sh ‐p 8091 ‐n 1
bin/seata‐server.sh ‐p 8092 ‐n 2
bin/seata‐server.sh ‐p 8093 ‐n 3

启动成功,默认端口8091

在注册中心中可以查看到seata-server注册成功


Seata Client 快速开始

声明式事务实现(@GlobalTransactional)

接入微服务应用

业务场景:

用户下单,整个业务逻辑由二个微服务构成:

  • 订单服务:根据采购需求创建订单。        
  • 库存服务:对给定的商品扣除库存数量。

启动Seata server端,Seata server使用nacos作为配置中心和注册中心(上一步已完成)

配置微服务整合 seata

第一步:添加pom依赖

<dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

第二步: 各微服务对应数据库中添加 undo_log表

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) 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(11)      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
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

第三步:修改register.conf,配置nacos作为registry.type&config.type,对应seata server也使用nacos

注意:需要指定group = "SEATA_GROUP",因为Seata Server端指定了group = "SEATA_GROUP" ,必须保证一致

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

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }

  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }

  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  #type = "file"
  type = "nacos"
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
  }
  
  file {
    name = "file.conf"
  }
}

如果出现这种问题:

一般大多数情况下都是因为配置不匹配导致的:

  1. 检查现在使用的seata服务和项目maven中seata的版本是否一致
  2. 检查 tx-service-group,nacos.cluster,nacos.group 参数是否和Seata Server中的配置一致

跟踪源码:seata/discover包下实现了RegistryService#lookup,用来获取服务列表

NacosRegistryServiceImpl#lookup

String clusterName = getServiceGroup(key); #获取seata server集群名称

List<Instance> firstAllInstances = getNamingInstance().getAllInstances(getServiceName(), getServiceGroup(), clusters)

第四步:修改application.yml配置

配置seata 服务事务分组,要与服务端nacos配置中心中service.vgroup_mapping的后缀对应 

server:
  ## 启动端口
  port: 8150

# 数据源
spring:
  datasource:
    username: root
    password: zlp123456
    url: jdbc:mysql://47.103.20.21:3307/zlp-mall?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #初始化时运行sql脚本
    schema: classpath:sql/schema.sql
    initialization-mode: never
  application:
    name: stock-seata
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
    alibaba:
      seata:
        tx-service-group: my_test_tx_group #配置事务分组
#设置mybatis
mybatis:
  mapper-locations: classpath:/mapper/*Mapper.xml
  #config-location: classpath:mybatis-config.xml
  typeAliasesPackage: com.zlp.seata.order.pojo
  configuration:
    mapUnderscoreToCamelCase: true

seata:
  registry:
    # 配置seata的注册中心, 告诉seata client 怎么去访问seata server(TC)
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848  # seata server 所在的nacos服务地址
      application: seata-server    # seata server 的服务名seata-server ,如果没有修改可以不配
      username: nacos
      password: nacos
      group: SEATA_GROUP          # seata server 所在的组,默认就是SEATA_GROUP,没有改也可以不配
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      group: SEATA_GROUP
logging:
  level:
    com.zlp.seata.stock.mapper: debug

第五步 微服务发起者(TM 方)需要添加@GlobalTransactional注解

 /**
     * 下单
     * @date 2021-12-1 14:51:18
     */
    @Override
    @GlobalTransactional
    public Order create(Order order) {

        log.info("create.req order={}", JSON.toJSONString(order));
        // 插入订单
        orderMapper.insert(order);
        // 扣减库存 能否成功?
        stockService.reduct(order.getProductId());
        // 异常
        int a = 1 / 0;
        return order;
    }

第六步 测试结果

没有添加 @GlobalTransactional

模拟正常下单、扣库存异常;结果是订单数据库插入成功,扣库存失败

添加 @GlobalTransactional

模拟下单扣库存成功、扣余额失败,订单事务和库存事务正常回滚


  项目地址https://gitee.com/gaibianzlp/springcould-alibaba-example.githttps://gitee.com/gaibianzlp/springcould-alibaba-example.githttps://gitee.com/gaibianzlp/springcould-alibaba-example.git点关注不迷路,觉得对你有帮助请给一个赞或者长按一键三连,谢谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值