• Spring Cloud Alibaba 整合 Seata 实现分布式事务


    Spring Boot单体服务中,添加@Transactional注解就能实现事务。在单体服务中,执行事务都是在同一个数据库下进行。但是随着业务越来越复杂,数据量越来越大会进行分库分表。在微服务场景下,每个服务都有自己的数据库。之前的单体事务无法处理跨库的事务,这个时候就需要使用分布式事务。

    前面 Seata 环境搭建 介绍了seata的安装,安装后就需要结合实战项目介绍分布式事务的应用。

    版本

    • spring-cloud-starter-alibaba:2.1.1.RELEASE
    • spring-cloud-alibaba-dependencies:2.1.1.RELEASE
    • Seata Server 1.5.2
    • Nacos Server:2.0.1

    不同的版本实现可能不太相同,尽量保持版本一致。

    效果展现

    用户下单的业务逻辑,整个逻辑有两个微服务提供支持:

    • 仓库服务:给对应的商品扣减库存
    • 订单服务:创建订单

    用户下单购买商品。

    • 先创建订单,创建订单之后,再扣库存。
    • 如果库存不够,就回滚订单。

    搭建服务

    项目结构

    有三个项目,仓库服务stock、订单服务orderseata服务。seata服务先调用订单服务,然后调用仓库服务。

    maven 依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-dependenciesartifactId>
        <version>2.1.1.RELEASEversion>
        <type>pomtype>
        <scope>importscope>
    dependency>
    
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-alibaba-dependenciesartifactId>
        <version>2.1.1.RELEASEversion>
        <type>pomtype>
        <scope>importscope>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-dependenciesartifactId>
        <version>$Greenwich.SR3version>
        <type>pomtype>
        <scope>importscope>
    dependency>
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    <dependency>
       <groupId>com.alibaba.cloudgroupId>
       <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-seataartifactId>
    dependency>
    <dependency>
        <groupId>io.seatagroupId>
        <artifactId>seata-spring-boot-starterartifactId>
    dependency>
    

    三个项目的依赖都是一样的。

    yml 配置

    seata需要分别配置配置中心注册中心

    • 配置中心内部放置着各种配置文件,你可以通过自己所需进行获取配置加载到对应的客户端.比如Seata Client端(TM,RM),Seata Server(TC),会去读取全局事务开关,事务会话存储模式等信息。

    • 注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用.比如Seata Client端(TM,RM),发现Seata Server(TC)集群的地址,彼此通信.

    从官网文档找到Nacos 配置中心,在application.yml加入对应的配置:

    seata:
      config:
        type: nacos
        nacos:
          server-addr: xxxx
          group: SEATA_GROUP
          namespace: xxxxxx
          username: nacos
          password: nacos
    

    groupnamespaceNacos控制中心页面可以找到,分别对应下图:

    Nacos注册中心同样可以获取如下配置:

    seata:
      registry:
        type: nacos
        nacos:
          application: seata-server
          server-addr: 127.0.0.1:8848
          group : SEATA_GROUP
          namespace: xxxxx
          username: nacos
          password: nacos
    

    application对应的是Seata Server配置的服务名。Seata Server将自己封装成一个微服务注册到Nacos注册中心。其他配置和配置中心配置一致。

    仓库服务

    全部贴代码,文章也比较繁琐。所以使用接口的方式,简单声明代码即可。

    仓库扣减库存:

    public interface StockService {
    
        /**
         * 减库存
         * @param id    商品id
         * @param num   扣减数量
         */
        void reduceStock(Long id, BigDecimal num);
    }
    

    订单服务

    创建订单:

    public interface OrderService {
    
        /**
         * 创建订单
         * @param num     下单数量
         * @param price   商品价格
         * @param goodsId 商品id
         */
        Order create(BigDecimal num,BigDecimal price,Long goodsId);
    }
    

    下单操作:

    先创建订单,然后扣减库存

    @GlobalTransactional(rollbackFor = Exception.class)
    public void order(Long goodsId) {
        orderService.create(BigDecimal.TEN,BigDecimal.TEN,goodsId);
        stockService.reduceStock(goodsId,BigDecimal.TEN);
    }
    

    数据不回滚

    调用下单服务,库存不够,报错了,但是创建订单不会回滚。

    分析原因

    事务没有回滚,从官网示例下载到本地运行,却可以回滚,对比两者控制台输出之后,发现官网示例使用了mybatis-plus自动添加了DataSourceProxy数据源代理。

    所以需要配置seata代理数据源。

    配置数据源代理

    配置Druid数据源代理:

    @Configuration
    public class DataSourceProxyConfig {
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource druidDataSource(){
            return new DruidDataSource();
        }
    
        @Bean
        public DataSourceProxy dataSourceProxy(DataSource dataSource) {
            return new DataSourceProxy(dataSource);
        }
    
        @Bean
        public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dataSourceProxy);
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources("classpath*:/mapper/*.xml"));
            sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
            return sqlSessionFactoryBean.getObject();
        }
    
    }
    

    同时需要关闭数据源自动代理:

    seata.enable-auto-data-source-proxy: false
    

    再调用下单接口,仓库报错报错,创建订单回滚。订单服务控制台也提示了数据回滚:

    2023-03-01 23:49:32.162  WARN 66509 --- [nio-8040-exec-3] c.a.c.seata.web.SeataHandlerInterceptor  : xid in change during RPC from 192.168.31.115:8091:1864853653168447501 to null
    2023-03-01 23:49:33.653  INFO 66509 --- [h_RMROLE_1_3_24] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=192.168.31.115:8091:1864853653168447501,branchId=1864853653168447502,branchType=AT,resourceId=jdbc:mysql://34.80.215.211:3306/test,applicationData=null
    2023-03-01 23:49:33.653  INFO 66509 --- [h_RMROLE_1_3_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 192.168.31.115:8091:1864853653168447501 1864853653168447502 jdbc:mysql://34.80.215.211:3306/test
    2023-03-01 23:49:36.005  INFO 66509 --- [h_RMROLE_1_3_24] i.s.r.d.undo.AbstractUndoLogManager      : xid 192.168.31.115:8091:1864853653168447501 branch 1864853653168447502, undo_log deleted with GlobalFinished
    2023-03-01 23:49:36.393  INFO 66509 --- [h_RMROLE_1_3_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked
    

    PhaseTwo_Rollbacked表示两阶段回滚。

    报错 can not get cluster name

    控制台报错:

    can not get cluster name in registry config 'service.vgroupMapping.nacos-provide-order-seata-service-group', please make sure registry config correct
    can not get cluster name in registry config 'service.vgroupMapping.nacos-provide-order-seata-service-group', please make sure registry config correct
    can not get cluster name in registry config 'service.vgroupMapping.nacos-provide-order-seata-service-group', please make sure registry config correct
    

    服务在Nacos配置中心找不到service.vgroupMapping.nacos-provide-order-seata-service-group,这是在找分组事务,官网文档有如下介绍:

    配置中心有service.vgroupMapping.default_tx_group配置文件,根据上图讲解,需要在application.yml配置:

    seata:
       tx-service-group: default_tx_group
    

    配置后还是报错。定位到报错的源码,向上调式源码,在application.yml配置:

    spring:
      cloud:
        alibaba:
          seata:
            tx-service-group: default_tx_group
    

    总结

    本文介绍了Spring Cloud整合分布式事务seta,主要有:

    • 添加相关依赖
    • 配置application.yml配置,主要添加nacos配置中心和注册中心的配置。
    • 实现一个下单服务,先创建订单,然后扣减库存。库存不够,创建订单回滚。

    搭建服务完成之后,事务不回滚,对比官网实例项目。需要添加数据源代理,同时关闭据源自动代理。分布式事务就生效了。

    控制台一直报错can not get cluster name in registry config,通过调试源码,找到问题的根源,这里学到了通过源码解决问题。以前前段时间一直在看源码,这次通过源码解决问题,努力还是会有收获的

    源码

  • 相关阅读:
    听说你想面对监狱编程,你,够格吗?
    GateWay实现负载均衡
    RPA技术介绍与应用价值
    面试突击:Spring 依赖注入有几种?各有什么优缺点?
    uni-app 实现拍照后给照片加水印功能
    APP自动化测试-11.webview技术原理
    Bootstrap关于盒子(盒模型)边距的设置
    【Linux】进程概念 —— PCB
    【Linux】ls命令
    计算机毕业设计 SSM在线心理评测系统 心理咨询系统 在线心理测试系统Java Vue MySQL数据库 远程调试 代码讲解
  • 原文地址:https://www.cnblogs.com/jeremylai7/p/17172220.html