• 《SpringBoot+Dubbo+Seata分布式事务实战》


    Seata 是 阿里巴巴开源的分布式事务中间件,以高效并且对业务0侵入的方式,解决微服务场景下面临的分布式事务问题。

    事实上,官方在GitHub 已经给出了多种环境下的Seata 应用示例项目,地址:
    GitHub - seata/seata-samples: seata-samples 。

    为什么要重新写一遍呢,主要原因有两点:

    • 官网代码示例中,依赖太多,分不清哪些有什么作用
    • Seata相关资料较少,笔者在搭建的过程中,遇到了一些坑,记录一下

    一、环境准备

    本文涉及软件环境如下:

    • SpringBoot 2.1.6.RELEASE
    • Dubbo 2.7.1
    • Mybatis 3.5.1
    • Seata 0.6.1
    • Zookeeper 3.4.10

    1、业务场景

    为了简化流程,我们只需要订单和库存两个服务。创建订单的时候,调用库存服务,扣减库存。

    涉及的表设计如下:

    1. <pre style="box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">CREATE TABLE `t_order` (
    2. `id` int(11) NOT NULL AUTO_INCREMENT,
    3. `order_no` varchar(255) DEFAULT NULL,
    4. `user_id` varchar(255) DEFAULT NULL,
    5. `commodity_code` varchar(255) DEFAULT NULL,
    6. `count` int(11) DEFAULT '0',
    7. `amount` double(14,2) DEFAULT '0.00',
    8. PRIMARY KEY (`id`)
    9. ) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8;
    10. CREATE TABLE `t_storage` (
    11. `id` int(11) NOT NULL AUTO_INCREMENT,
    12. `commodity_code` varchar(255) DEFAULT NULL,
    13. `name` varchar(255) DEFAULT NULL,
    14. `count` int(11) DEFAULT '0',
    15. PRIMARY KEY (`id`),
    16. UNIQUE KEY `commodity_code` (`commodity_code`)
    17. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

    另外还需要一个回滚日志表:

    1. <pre style="box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">CREATE TABLE `undo_log` (
    2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
    3. `branch_id` bigint(20) NOT NULL,
    4. `xid` varchar(100) NOT NULL,
    5. `rollback_info` longblob NOT NULL,
    6. `log_status` int(11) NOT NULL,
    7. `log_created` datetime NOT NULL,
    8. `log_modified` datetime NOT NULL,
    9. `ext` varchar(100) DEFAULT NULL,
    10. `context` varchar(100) DEFAULT NULL,
    11. PRIMARY KEY (`id`),
    12. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    13. ) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8;

    2、Seata下载安装

    打开
    Releases · seata/seata · GitHub ,目前最新版本是v0.6.1 。

    下载解压后,到seata-server-0.6.1\distribution\bin 目录下可以看到seata-server.bat和seata-server.sh ,选择一个双击执行。

    不出意外的话,当你看到-Server started ... 等字样,就正常启动了。

    3、Maven依赖

    由于是Dubbo项目,我们先引入Dubbo相关依赖。

    1. <pre style="box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;"><dependency>
    2. <groupId>org.apache.dubbogroupId>
    3. <artifactId>dubboartifactId>
    4. <version>2.7.1version>
    5. dependency>
    6. <dependency>
    7. <groupId>org.apache.dubbogroupId>
    8. <artifactId>dubbo-spring-boot-starterartifactId>
    9. <version>2.7.1version>
    10. dependency>

    Dubbo的服务要注册到Zookeeper,引入curator客户端。

    1. <pre style="box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;"><dependency>
    2. <groupId>org.apache.curatorgroupId>
    3. <artifactId>curator-frameworkartifactId>
    4. <version>2.13.0version>
    5. dependency>
    6. <dependency>
    7. <groupId>org.apache.curatorgroupId>
    8. <artifactId>curator-recipesartifactId>
    9. <version>2.13.0version>
    10. dependency>

    最后,引入Seata。

    1. <pre style="box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;"><dependency>
    2. <groupId>io.seatagroupId>
    3. <artifactId>seata-allartifactId>
    4. <version>0.6.1version>
    5. dependency>

    当然了,还有其他的如Mybatis、mysql-connector 等就不粘了,自行引入即可。

    二、项目配置

    1、application.properties

    这里只需要配置数据库连接信息和Dubbo相关信息即可。

    1. "box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">server.port=8011
    2. spring.datasource.url=jdbc:mysql://127.0.0.1:3306/seata
    3. spring.datasource.username=root
    4. spring.datasource.password=root
    5. dubbo.application.name=order-service
    6. dubbo.registry.address=zookeeper://127.0.0.1:2181
    7. dubbo.protocol.name=dubbo
    8. dubbo.protocol.port=20881
    9. dubbo.consumer.timeout=9999999
    10. dubbo.consumer.check=false

    2、数据源

    Seata 是通过代理数据源实现事务分支,所以需要先配置一个数据源的代理,否则事务不会回滚。

    1. "box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">@Bean
    2. public DataSourceProxy dataSourceProxy(DataSource dataSource) {
    3. return new DataSourceProxy(dataSource);
    4. }

    注意,这里的DataSourceProxy 类位于io.seata.rm.datasource 包内。

    3、Seata配置

    还需要配置全局事务扫描器。有两个参数,一个是应用名称,一个是事务分组。

    1. "box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">@Bean
    2. public GlobalTransactionScanner globalTransactionScanner() {
    3. return new GlobalTransactionScanner("springboot-order", "my_test_tx_group");
    4. }

    事实上,关于Seata事务的一系列初始化工作都在这里完成。

    4、配置注册中心

    Seata 连接到服务器的时候需要一些配置项,这时候有一个registry.conf 文件可以指定注册中心和配置文件是什么。

    这里有很多可选性,比如file、nacos 、apollo、zk、consul 。

    后面4个都是业界成熟的配置注册中心产品,为啥还有个file呢?

    官方的初衷是在不依赖第三方配置注册中心的基础上快速集成测试seata 功能,但是file 类型本身不具备注册中心的动态发现和动态配置功能。

    registry.conf 文件内容如下:

    1. "box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">registry {
    2. type = "file"
    3. file {
    4. name = "file.conf"
    5. }
    6. }
    7. config {
    8. # file、nacos 、apollo、zk、consul
    9. type = "file"
    10. file {
    11. name = "file.conf"
    12. }
    13. }

    如果你选择了file 类型,通过name属性指定了file.conf ,这个文件中指定了客户端或服务器的配置信息。比如传输协议、服务器地址等。

    1. "box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">service {
    2. #vgroup->rgroup
    3. vgroup_mapping.my_test_tx_group = "default"
    4. #only support single node
    5. default.grouplist = "127.0.0.1:8091"
    6. #degrade current not support
    7. enableDegrade = false
    8. #disable
    9. disable = false
    10. }

    三、业务代码

    1、库存服务

    在库存服务中,拿到商品编码和购买总个数,扣减即可。

    1. "box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">id="decreaseStorage">
    2. update t_storage set count = count-${count} where commodity_code = #{commodityCode}

    然后用Dubbo将库存服务扣减接口暴露出去。

    2、订单服务

    在订单服务中,先扣减库存,再创建订单。最后抛出异常,然后去数据库检查事务是否回滚。

    1. "box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">@GlobalTransactional
    2. public void createOrder(OrderDTO orderDTO) {
    3. System.out.println("开始全局事务。XID="+RootContext.getXID());
    4. StorageDTO storageDTO = new StorageDTO();
    5. storageDTO.setCount(orderDTO.getCount());
    6. storageDTO.setCommodityCode(orderDTO.getCommodityCode());
    7. //1、扣减库存
    8. storageDubboService.decreaseStorage(storageDTO);
    9. //2、创建订单
    10. orderDTO.setId(order_id.incrementAndGet());
    11. orderDTO.setOrderNo(UUID.randomUUID().toString());
    12. Order order = new Order();
    13. BeanUtils.copyProperties(orderDTO,order);
    14. orderMapper.createOrder(order);
    15. throw new RuntimeException("分布式事务异常..."+orderDTO.getOrderNo());
    16. }

    值得注意的是,在订单服务事务开始的方法上,需要标注@GlobalTransactional 。另外,在库存服务的方法里,不需要此注解,事务会通过Dubbo进行传播。

    四、注意事项

    1、数据源

    请切记,Seata 是通过代理数据源实现事务分支,一定不要忘记配置数据源代理。

    2、主键自增

    在数据库中,表里的主键ID字段都是自增的。如果你的字段不是自增的,那么在Mybatis的insert SQL 中,要将列名写完整。

    比如我们可以这样写SQL:

    <pre style="box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">INSERT INTO table_name VALUES (值1, 值2,....)
    

    那么这时候就要写成:

    <pre style="box-sizing: border-box; font-family: monospace; font-size: 1em; margin: 20px 0px; padding: 15px; border: 0px; background-color: rgb(244, 245, 246); white-space: pre-wrap; word-break: break-all;">INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
    

    3、序列化问题

    在订单表中,amount 字段类型为double 。在seata0.6.1 版本中,默认的序列化方式为fastjson ,但它会将这个字段序列化成bigdecimal 类型,会导致后面类型不匹配。

    但是在后续的seata0.7.0 版本中(还未发布),已经将默认的序列化方式改为了jackson 。

    不过无需担心,这个问题一般不会出现。笔者是因为引错了一个包,才导致发现这问题。

  • 相关阅读:
    v-decorator和v-model的使用对比
    基于element-plus的选择组件el-select实现下拉加载封装
    安利2款好用的笔记软件给你们
    找不到mfc100u.dll怎么解决,总结了多种修复方法帮你解决
    【数据结构与算法】链表的分割
    Vim 突然报错:E576: viminfo: 缺少 ‘>‘ 位于行
    Gradient Descent
    MyBatis--获取参数值
    MVCC的执行原理
    前端-naive ui如何渲染表格中的开关switch、单选框checkbox、按钮
  • 原文地址:https://blog.csdn.net/weixin_62421895/article/details/126378535