• 【Spring Cloud Alibaba】seata分布式事务官方入门案例(实战版)



    本文是介绍官方的seata入门案例,采用的是 微服务架构,参考地址:https://seata.io/zh-cn/docs/user/quickstart/。

    代码详细地址:https://github.com/seata/seata-samples/tree/master/dubbo。

    整个seata-samples代码库很大,本例只用到了dubbo子模块。

    seata分布式事务官方入门案例导读1(实战版)
    seata分布式事务官方入门案例导读2(实战版)

    1. 业务介绍

    1.1. 用例

    本业务以购买商品的业务逻辑为例子。整个业务涉及到3个微服务一起提供服务:

    • 仓储服务:对给定的商品扣除仓储数量。
    • 订单服务:根据采购需求创建订单。
    • 帐户服务:从用户帐户中扣除余额。

    1.2. 架构图

    Architecture

    1.3. 3个服务的代码及业务逻辑(略)

    2. SEATA 的分布式交易解决方案

    img

    我们只需要使用一个 @GlobalTransactional 注解在业务方法上:

    
        @GlobalTransactional
        public void purchase(String userId, String commodityCode, int orderCount) {
            ......
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    备注:

    1、每个微服务都与seata服务连接起来(可以看每个服务的xxx-service.xml)。如dubbo-storage-service.xml文件。其他服务也一样

        <bean id="storageDataSourceProxy" class="io.seata.rm.datasource.DataSourceProxy">
            <constructor-arg ref="storageDataSource" />
        bean>
        <bean name="storageDataSource" class="com.alibaba.druid.pool.DruidDataSource"
              init-method="init" destroy-method="close">
            <property name="url" value="${jdbc.storage.url}"/>
            <property name="username" value="${jdbc.storage.username}"/>
            <property name="password" value="${jdbc.storage.password}"/>
            <property name="driverClassName" value="${jdbc.storage.driver}"/>
        bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、每个服务都注册了一个GlobalTransactionScanner,字符串dubbo-demo-storage-service表示应用ID(applicationId),字符串my_test_tx_group表示事务服务组(txServiceGroup)

        <bean class="io.seata.spring.annotation.GlobalTransactionScanner">
            <constructor-arg value="dubbo-demo-storage-service"/>
            <constructor-arg value="my_test_tx_group"/>
        bean>
    
    • 1
    • 2
    • 3
    • 4

    3、字符串my_test_tx_group还在项目的file.conf文件中还用到了。

    3. 由Dubbo + SEATA提供支持的示例(实战)

    建议参考代码https://github.com/seata/seata-samples/tree/master/dubbo的readme.md文件。

    3.1. 步骤 1:建立数据库,如seata数据库

    3.2. 步骤 2:创建 UNDO_LOG 表

    undo_log表是临时中间表,存储的是分布式事务过程中每张表变更前后的值。一条记录代表某张表变更前和变更后的详细数据。这张表的作用是需要读者理解的。

    之所以记录变更前后的详细数据,就是为了在事务失败时能够进行回滚。

    -- 注意此处0.3.0+ 增加唯一索引 ux_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,
      `ext` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.3. 步骤 3:为示例业务创建表

    3.4. 步骤 4: 启动服务

    # 启动seata服务
    docker run --name seata-file -p 8091:8091 hellowoodes/seata:0.9.0-file
    # 启动zooekper
    docker run --name zookeeper -p 2181:2181 -p 2888:2888 -p 3888:3888 -d zookeeper
    
    • 1
    • 2
    • 3
    • 4

    3.5. 步骤 5: 运行示例

    注意:作者下载代码测试的时候,以下应用都不能启动,后查阅资料,发现是缺少了jar包,请在启动出问题的时候在pom.xml中添加jar包。

    <dependency>
        <groupId>com.alibaba.springgroupId>
        <artifactId>spring-context-supportartifactId>
        <version>1.0.11version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 启动 DubboAccountServiceStarter
    • 启动 DubboStorageServiceStarter
    • 启动 DubboOrderServiceStarter
    • 运行 DubboBusinessTester for demo test

    3.6. 测试

    一共设计到3张表,初始状态下,库存storage_tbl存储了100个库存,订单表order_tbl为空,账户表account_tbl有999元。测试类模拟了用户购买了2件200元的商品。

    3.6.1. 正常执行

    如果一切正常,那么:

    1、account_tbl表账户余额为999 - 200*2 = 599元

    2、order_tbl表会生成一个订单

    3、storage_tbl表的库存为100 - 2 = 98

    测试结果如下图:

    image-20231023010711440

    3.6.2. 模拟异常发生

    在业务方法上加了@GlobalTransactional注解,如果分布式事务生效,那么在发生异常Exception时,3张表的数据都会回滚,值还是初始值,即:

    1、account_tbl表为999元

    2、order_tbl是空的

    3、storage_tbl是100件

    添加模拟发生异常的代码:

    image-20231023011144154

    查看测试结果如下图:

    观察到控制台发生了一场,且数据库中3张表的数据没有变动。

    image-20231023011713581

    3.6.3. 原理简述

    全部3个微服务全部debug启动,且基于架构图,Account服务是最后调用的,在该服务上打上一个断点。然后去观察undo_log表中的内容。注意超时时间哦。

    1、打上断点

    在io.seata.samples.dubbo.service.impl.OrderServiceImpl#create的最后位置打上断点

    2、观察undo_log表

    image-20231023012240702

    备注:刚好3条记录,对应3张表变更前后的内容;blob的内容查看需要再navicat中设置一下;然后复制格式化成json。

    3、观察3张表的数据

    下面是账户余额表account_tbl变更前后的数据情况(json格式化后):

    {
      "@class": "io.seata.rm.datasource.undo.BranchUndoLog",
      "xid": "172.17.0.4:8091:2151716199",
      "branchId": 2151716201,
      "sqlUndoLogs": [
        "java.util.ArrayList",
        [
          {
            "@class": "io.seata.rm.datasource.undo.SQLUndoLog",
            "sqlType": "UPDATE",
            "tableName": "account_tbl",
            "beforeImage": {
              "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
              "tableName": "account_tbl",
              "rows": [
                "java.util.ArrayList",
                [
                  {
                    "@class": "io.seata.rm.datasource.sql.struct.Row",
                    "fields": [
                      "java.util.ArrayList",
                      [
                        {
                          "@class": "io.seata.rm.datasource.sql.struct.Field",
                          "name": "id",
                          "keyType": "PRIMARY_KEY",
                          "type": 4,
                          "value": 2
                        },
                        {
                          "@class": "io.seata.rm.datasource.sql.struct.Field",
                          "name": "money",
                          "keyType": "NULL",
                          "type": 4,
                          "value": 999
                        }
                      ]
                    ]
                  }
                ]
              ]
            },
            "afterImage": {
              "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
              "tableName": "account_tbl",
              "rows": [
                "java.util.ArrayList",
                [
                  {
                    "@class": "io.seata.rm.datasource.sql.struct.Row",
                    "fields": [
                      "java.util.ArrayList",
                      [
                        {
                          "@class": "io.seata.rm.datasource.sql.struct.Field",
                          "name": "id",
                          "keyType": "PRIMARY_KEY",
                          "type": 4,
                          "value": 2
                        },
                        {
                          "@class": "io.seata.rm.datasource.sql.struct.Field",
                          "name": "money",
                          "keyType": "NULL",
                          "type": 4,
                          "value": 599
                        }
                      ]
                    ]
                  }
                ]
              ]
            }
          }
        ]
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    下面是订单表order_tbl:

    {"@class":"io.seata.rm.datasource.undo.BranchUndoLog","xid":"172.17.0.4:8091:2151716242","branchId":2151716245,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"INSERT","tableName":"order_tbl","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords","tableName":"order_tbl","rows":["java.util.ArrayList",[]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"order_tbl","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":4,"value":6},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"user_id","keyType":"NULL","type":12,"value":"U100001"},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"commodity_code","keyType":"NULL","type":12,"value":"C00321"},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"count","keyType":"NULL","type":4,"value":2},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"money","keyType":"NULL","type":4,"value":400}]]}]]}}]]}
    
    • 1

    下面是库存表storage_tbl:

    {"@class":"io.seata.rm.datasource.undo.BranchUndoLog","xid":"172.17.0.4:8091:2151716242","branchId":2151716243,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"UPDATE","tableName":"storage_tbl","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"storage_tbl","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":4,"value":2},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"count","keyType":"NULL","type":4,"value":100}]]}]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"storage_tbl","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":4,"value":2},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"count","keyType":"NULL","type":4,"value":98}]]}]]}}]]}
    
    • 1
  • 相关阅读:
    学习笔记|Pearson皮尔逊相关系数|Spearman斯皮尔曼相关系数|和Kendall肯德尔tau-b相关系数|分析流程|-SPSS中双变量相关性分析系数
    java根据word模板生成文档(含文本替换和动态生成表格)
    专注区块链底层技术突破,“复杂美”用技术开源推动产业未来
    AWS无服务器 应用程序开发—第九章 文件存储(Amazon S3)
    苹果签名有多少种类之TF签名(TestFlight签名)是什么?优势是什么?什么场合需要应用到?
    淘宝运营方案
    外贸邮件群发
    工具: MarkDown学习
    TP5搭配layui2.x的form表单ajax提交
    【代码精读】ATF的启动流程
  • 原文地址:https://blog.csdn.net/yuchangyuan5237/article/details/133981021