美文网首页
分布式事务 Seata(五) 微服务间调用的事务处理

分布式事务 Seata(五) 微服务间调用的事务处理

作者: _大叔_ | 来源:发表于2020-08-11 09:50 被阅读0次

    关于nacos,seata-server,seata相关表等 创建配置请看我前面的文章。

    项目结构

    |-- demo
      |-- entity 实体对象(为了让其他服务拥有所有服务对象)
      |-- order 订单 (pom导入了 entity )
      |-- stock 库存 (pom导入了 entity )
      |-- user 用户 (pom导入了 entity )
    

    项目依赖

    entity

        <properties>
            <mybatis-plus>3.3.1.tmp</mybatis-plus>
            <db-drive>1.1.23</db-drive>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
            <!-- 数据持久相关配置 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus}</version>
            </dependency>
    
            <!-- 驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${db-drive}</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <!-- nacos 注册中心 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
    
            <!-- 多数据源,seata分布式事务 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
                <version>3.2.0</version>
            </dependency>
        </dependencies>
    

    order、stock、user

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>entity</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>io.seata</groupId>
                        <artifactId>seata-spring-boot-starter</artifactId>
                    </exclusion>
                </exclusions>
                <version>2.2.1.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
                <version>1.3.0</version>
            </dependency>
    
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
                <version>1.3.0</version>
            </dependency>
        </dependencies>
    

    配置文件

    因为里面有nacos服务注册,所以会有两个配置文件,但配置文件基本一样,所以我就挑user服务的。

    bootstrap.properties

    注意 spring.application.name 不同服务这里是不一样的

    #spring.profiles.active=dev
    # Nacos Server 的地址
    #spring.cloud.nacos.config.server-addr=0.0.0.0:8848
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    # 这里的对应 Nacos Server 中的 Data ID 的前缀
    spring.application.name=user-server
    # 这里的对应 Nacos Server  中的指定的配置规则格式和 Data ID 的后缀
    spring.cloud.nacos.config.file-extension=yaml
    # 关闭动态刷新,默认是开启
    spring.cloud.nacos.config.refresh.enabled=false
    
    # 服务发现
    #spring.cloud.nacos.discovery.server-addr=0.0.0.0:8848
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    # 值范围:1到100。值越大,重量越大。
    spring.cloud.nacos.discovery.weight=1
    # 集群名称
    spring.cloud.nacos.discovery.cluster-name=user
    # 心跳间隔 10秒
    spring.cloud.nacos.discovery.heart-beat-interval=10000
    # sentinel 日志
    # spring.cloud.sentinel.log.dir=./sentinel/log
    

    application.yml

    以下为多数据源配置,但相关服务只需要配置相关库就行。

    server:
      port: 8082
    
    spring:
      main:
        allow-bean-definition-overriding: true
      autoconfigure:
        exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
      datasource:
        druid:
          stat-view-servlet:
            enabled: true
            url-pattern: "/druid/*"
            allow: 127.0.0.1
            deny: 192.168.1.73
            reset-enable: true
            login-username: admin
            login-password: admin@2020
          web-stat-filter:
            enabled: true
            url-pattern: "/*"
            exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
        dynamic:
          druid:
            filters: stat,wall
            initial-size: 10
            min-idle: 10
            maxActive: 200
            maxWait: 10000
            useUnfairLock: true
            validation-query: 'select 1'
            testWhileIdle: true
            testOnBorrow: false
            testOnReturn: false
          primary: user
          # 启用严格模式
          strict: true
          seata: true
          seata-mode: AT
          datasource:
            user:
              url: jdbc:mysql://0.0.0.0:3306/user?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&&serverTimezone=Asia/Shanghai
              driver-class-name: com.mysql.cj.jdbc.Driver
              type: com.alibaba.druid.pool.DruidDataSource
              username: dev
              password: mysql@dev.2020
              # 建表脚本
              # schema: classpath:db/schema-account.sql
    #        order:
    #          url: jdbc:mysql://0.0.0.0:3306/order?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&&serverTimezone=Asia/Shanghai
    #          driver-class-name: com.mysql.cj.jdbc.Driver
    #          type: com.alibaba.druid.pool.DruidDataSource
    #          username: dev
    #          password: mysql@dev.2020
    #        stock:
    #          url: jdbc:mysql://0.0.0.0:3306/stock?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&&serverTimezone=Asia/Shanghai
    #          driver-class-name: com.mysql.cj.jdbc.Driver
    #          type: com.alibaba.druid.pool.DruidDataSource
    #          username: dev
    #          password: mysql@dev.2020
    
    seata:
      enabled: true
      # 启用自动代理数据源
      enable-auto-data-source-proxy: false
      # 随便起个名字,但最好与服务名称一致
      application-id: ${spring.application.name}
      # 此处的名称一定要与 vgroup-mapping下配置的参数保持一致
      tx-service-group: my_test_tx_group
      # 目的是找 seata 的配置
      config:
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848
          username: nacos
          password: nacos@root@2020
          namespace:
          group: SEATA_GROUP
      # registry 目的是找 seata-server 服务
      registry:
        type: nacos
        nacos:
          application: seata-server
          server-addr: 127.0.0.1:8848
          username: nacos
          password: nacos@root@2020
          namespace:
    

    代码

    关于实体表的结构在上一篇文章已经有了,这里直接跳过。

    entity

    entity 被所有服务所引用,所以这里直接写 Feign接口是最方便的。

    @FeignClient(value = "user-server",path = "/account")
    public interface AccountApi {
    
        @PostMapping("/debit")
        void debit(@RequestParam("uid") Integer uid, @RequestParam("money") Integer money);
    }
    
    @FeignClient(value = "order-server",path = "/order")
    public interface OrderApi {
    
        @PostMapping("/create")
        void create(@RequestParam("uid") Integer uid,
                           @RequestParam("stockId") Integer stockId,
                           @RequestParam("count") Integer count,
                           @RequestParam("money") Integer money
        );
    }
    
    @FeignClient(value = "stock-server",path = "/stock")
    public interface StockApi {
    
        @PostMapping("/deduct")
        Integer deduct(@RequestParam("stockId") Integer stockId, @RequestParam("count") Integer count);
    }
    

    order,stock,user

    关于 order,stock,user的mapper 和 server 已经在上一篇写过了,只需要把他们放到对应的服务里面即可。然后修改实现类,把里面调用其他的server改成调用 xxxApi即可,在添加一层 controller。以order为例:

    @Service
    public class OrderServerImpl implements OrderServer {
    
        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private AccountApi accountApi;
    
        @Override
        public void create(Integer uid, Integer stockId, Integer count,Integer money) {
            accountApi.debit(uid,money);
            Order order = new Order();
            order.setUid(uid);
            order.setTime(new Date());
            order.setCount(count);
            order.setMoney(money);
            if(1==1){
                throw new RuntimeException("下单异常");
            }
            orderMapper.insert(order);
        }
    }
    
    @RestController
    @RequestMapping("/order")
    public class OrderController {
    
        @Autowired
        private OrderServer orderServer;
    
        @PostMapping("/create")
        public void create(@RequestParam("uid") Integer uid,
                           @RequestParam("stockId") Integer stockId,
                           @RequestParam("count") Integer count,
                           @RequestParam("money") Integer money
        ){
            orderServer.create(uid, stockId, count, money);
        }
    }
    

    这里我们可以看到 order里面其实有抛异常的动作。
    再说一遍我们的测试方案是:

    采购开始 -> 减库存,得到所需花费金额 -> 用户减去所需花费金额 -> 创建订单
    

    编写调用类

    调用类我是在 user服务里面做的,方法为 purchase

    @RestController
    @RequestMapping("/account")
    public class AccountController {
    
        @Autowired
        private AccountServer accountServer;
        @Autowired
        private BusinessService businessService;
    
        @PostMapping("/debit")
        public void debit(@RequestParam("uid") Integer uid, @RequestParam("money") Integer money){
            accountServer.debit(uid, money);
        }
    
        @PostMapping("/purchase")
        public void purchase(@RequestParam("uid") Integer uid,@RequestParam("stockId") Integer stockId ,@RequestParam("count") Integer count){
            businessService.purchase(uid, stockId,count);
        }
    
    }
    

    再贴一下 BusinessServiceImpl

    @Service
    public class BusinessServiceImpl implements BusinessService {
    
        @Autowired
        private StockApi stockApi;
        @Autowired
        private OrderApi orderApi;
    
        @GlobalTransactional
        @Override
        public void purchase(Integer uid, Integer stockId, Integer count) {
            Integer money = stockApi.deduct(stockId, count);
            orderApi.create(uid, stockId, count,money);
        }
    }
    
    

    @GlobalTransactional 全局事务(分布式事务注解)

    启动所有服务

    服务全部启动成功之后,你会在nacos看到如下:

    测试

    这里直接测试下单异常,会不会出现订单没创建,但是已经扣库存和余额。

    检查数据库数据:

    stock:id=1,num=500,price=5
    account:id=1,amount=2000
    order:
    

    没有进行扣除,证明服务调用的分布式事务起作用的。

    相关文章

      网友评论

          本文标题:分布式事务 Seata(五) 微服务间调用的事务处理

          本文链接:https://www.haomeiwen.com/subject/qggjdktx.html