【Spring Cloud Alibaba】Seata 分布式事务

文章目录

  • 【Spring Cloud Alibaba】Seata 分布式事务
    • 1、Spring Cloud Alibaba Seata
    • 2、服务公共内容
      • (1)相关依赖
      • (2)application.yml
      • (3)file.conf、registry.conf
      • (4)AjaxResult
      • (5)代码生成
      • (6)创建模块数据库
    • 3、搭建账户服务
    • 4、搭建订单服务
    • 5、搭建库存服务
    • 6、测试下单业务
      • (1)检查服务启动结果
      • (2)正常情况下单
      • (3)出错情况下单
      • (4)增加 Seata 事物
    • 7、常见报错
      • (1)endpoint format should like ip:port
  • 微信公众号

【Spring Cloud Alibaba】Seata 分布式事务

1、Spring Cloud Alibaba Seata

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双 11,对各 BU 业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备

Seata 的官网,https://seata.io/zh-cn/
Spring Cloud 快速集成 Seata,https://github.com/seata/seata-samples/blob/master/doc/quick-integration-with-spring-cloud.md

本篇文章使用的是 Seata 的 AT 模式
业务需求:下订单 -> 减库存 -> 扣余额 -> 改订单状态

源码中的模块名称变更了,之前大意多了一个 boot,这里修改了一下

变更前 变更后
spring-cloud-alibaba-boot-seata-account spring-cloud-alibaba-seata-account
spring-cloud-alibaba-boot-seata-order spring-cloud-alibaba-seata-order
spring-cloud-alibaba-boot-seata-storage spring-cloud-alibaba-seata-storage

2、服务公共内容

公共内容需要在每个模块中都添加,除了 application.yml 有一点区别,其他所有配置相同

(1)相关依赖

三个模块的依赖相同

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>

        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
            <version>${spring-cloud-starter-openfeign.version}version>
        dependency>

        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-seataartifactId>
        dependency>

        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>${mybatis-plus-boot-starter.version}version>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>${lombok.version}version>
        dependency>

        <dependency>
            <groupId>com.spring4allgroupId>
            <artifactId>swagger-spring-boot-starterartifactId>
            <version>${swagger-spring-boot-starter.version}version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>

(2)application.yml

三个服务需要分别修改端口,数据库名字,其他的 application.yml 配置文件都一样

模块 端口 端点 数据库
spring-cloud-alibaba-boot-seata-account 8007 9007 alibaba-seata-account
spring-cloud-alibaba-boot-seata-order 8008 9008 alibaba-seata-order
spring-cloud-alibaba-boot-seata-storage 8009 9009 alibaba-seata-storage
# 应用配置
server:
  port: 8007

# 端点监控
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    jmx:
      exposure:
        include: '*'
    web:
      exposure:
        include: '*'
  server:
    port: 9007

spring:
  # 应用名称
  application:
    name: spring-cloud-alibaba-boot-seata-account
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/alibaba-seata-account?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    serialization:
      write-dates-as-timestamps: false
  # 微服务配置
  cloud:
    # Nacos配置
    nacos:
      discovery:
        namespace: sandbox-configuration
        password: nacos
        server-addr: localhost:8848
        username: nacos
    alibaba:
      # Seata配置
      seata:
        tx-service-group: tellsea_tx_group

# MybatisPlus配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    auto-mapping-behavior: full
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath*:mapper/**/*Mapper.xml

(3)file.conf、registry.conf

在你下载的 seata 的 bin,目录中复制到项目的 resource 目录下
【Spring Cloud Alibaba】Seata 分布式事务_第1张图片
并在 file.conf 文件中增加,与 store 同级,因为默认配置文件中没有

service {
  #vgroup->rgroup
  vgroupMapping.tellsea_tx_group = "default"
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
}

(4)AjaxResult

三个服务的 AjaxResult 相同

package cn.tellsea.entity;


import org.springframework.http.HttpStatus;
import org.springframework.util.ObjectUtils;

import java.util.HashMap;

/**
 * 公共返回值
 *
 * @author Tellsea
 * @date 2021/12/31
 */
public class AjaxResult<T> extends HashMap<String, Object> {
    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";
    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";
    /**
     * 数据对象
     */
    public static final String DATA_TAG = "data";
    private static final long serialVersionUID = 1L;

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     */
    public AjaxResult(int code, String msg) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, T data) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (!ObjectUtils.isEmpty(data)) {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult<Void> success() {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static <T> AjaxResult<T> success(T data) {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult<Void> success(String msg) {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static <T> AjaxResult<T> success(String msg, T data) {
        return new AjaxResult(HttpStatus.OK.value(), msg, data);
    }

    /**
     * 返回错误消息
     *
     * @return
     */
    public static AjaxResult<Void> error() {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult<Void> error(String msg) {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static <T> AjaxResult<T> error(String msg, T data) {
        return new AjaxResult(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg, data);
    }

    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg  返回内容
     * @return 警告消息
     */
    public static AjaxResult<Void> error(int code, String msg) {
        return new AjaxResult(code, msg, null);
    }

    public Integer getCode() {
        return (Integer) super.get(CODE_TAG);
    }

    public String getMsg() {
        return (String) super.get(MSG_TAG);
    }

    public T getData() {
        return (T) super.get(DATA_TAG);
    }
}

(5)代码生成

使用代码生成器,分别生成 controller、service、serviceImpl、mapper、mapper.xml 文件,不会使用的看上一篇文章

【Spring Cloud Alibaba】Mybatis Plus 代码生成器:https://mp.weixin.qq.com/s/9OZRbIqhLEOhH3QKEJWwPg

有不懂的地方,可以微信公众号留言,项目源码在:https://gitee.com/tellsea/spring-cloud-alibaba-learn

【Spring Cloud Alibaba】Seata 分布式事务_第2张图片

(6)创建模块数据库

【Spring Cloud Alibaba】Seata 分布式事务_第3张图片

3、搭建账户服务

创建 spring-cloud-alibaba-boot-seata-account 模块,先把第二节的账户服务都操作完成

控制层

package cn.tellsea.controller;


import cn.tellsea.entity.AjaxResult;
import cn.tellsea.service.IBizAccountService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

/**
 * 

* 账户表 前端控制器 *

* * @author Tellsea * @since 2021-12-31 */
@RestController @RequestMapping("/bizAccount") public class BizAccountController { @Autowired private IBizAccountService accountService; @ApiOperation("扣减账户") @PostMapping("/account/decrease") AjaxResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) { return accountService.decrease(userId, money); } }

接口层

package cn.tellsea.service;

import cn.tellsea.entity.AjaxResult;
import cn.tellsea.entity.BizAccount;
import com.baomidou.mybatisplus.extension.service.IService;

import java.math.BigDecimal;

/**
 * 

* 账户表 服务类 *

* * @author Tellsea * @since 2021-12-31 */
public interface IBizAccountService extends IService<BizAccount> { /** * 扣减账户 * * @param userId * @param money * @return */ AjaxResult decrease(Long userId, BigDecimal money); }

接口实现层

package cn.tellsea.service.impl;

import cn.tellsea.entity.AjaxResult;
import cn.tellsea.entity.BizAccount;
import cn.tellsea.mapper.BizAccountMapper;
import cn.tellsea.service.IBizAccountService;
import cn.tellsea.utils.BigDecimalUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

/**
 * 

* 账户表 服务实现类 *

* * @author Tellsea * @since 2021-12-31 */
@Slf4j @Service public class BizAccountServiceImpl extends ServiceImpl<BizAccountMapper, BizAccount> implements IBizAccountService { @Override public AjaxResult decrease(Long userId, BigDecimal money) { log.info("------->扣减余额开始"); //模拟超时异常,全局事务回滚 //暂停几秒钟线程 //try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } BizAccount account = baseMapper.selectById(userId); account.setResidue(BigDecimalUtils.subtract(account.getResidue(), money)); account.setUsed(BigDecimalUtils.add(account.getUsed(), money)); baseMapper.updateById(account); log.info("------->扣减余额结束"); return AjaxResult.success("扣减余额成功"); } }

账户模块需要增加一个 BigDecimalUtils 工具类

package cn.tellsea.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * 金额计算工具类
 *
 * @author Tellsea
 * @date 2021/12/31
 */
public class BigDecimalUtils {

    /**
     * 默认保存2位小数点
     */
    private static int POINTS = 2;

    /**
     * 默认进位方式
     */
    private static RoundingMode MODE = RoundingMode.CEILING;


    /**
     * 加
     *
     * @param a
     * @param b
     * @return
     */
    public static BigDecimal add(BigDecimal a, BigDecimal b) {
        if (a == null) {
            a = BigDecimal.valueOf(0.00);
        }
        if (b == null) {
            b = BigDecimal.valueOf(0.00);
        }
        return computer(1, a, b, POINTS, MODE);
    }

    public static BigDecimal add(BigDecimal a, BigDecimal b, int points, RoundingMode mode) {
        if (a == null) {
            a = BigDecimal.valueOf(0.00);
        }
        if (b == null) {
            b = BigDecimal.valueOf(0.00);
        }
        return computer(1, a, b, points, mode);
    }

    /**
     * 减
     *
     * @param a
     * @param b
     * @return
     */
    public static BigDecimal subtract(BigDecimal a, BigDecimal b) {
        if (a == null) {
            a = BigDecimal.valueOf(0.00);
        }
        if (b == null) {
            b = BigDecimal.valueOf(0.00);
        }
        return computer(2, a, b, POINTS, MODE);
    }

    public static BigDecimal subtract(BigDecimal a, BigDecimal b, int points, RoundingMode mode) {
        if (a == null) {
            a = BigDecimal.valueOf(0.00);
        }
        if (b == null) {
            b = BigDecimal.valueOf(0.00);
        }
        return computer(2, a, b, points, mode);
    }


    /**
     * 乘
     *
     * @param a
     * @param b
     * @return
     */
    public static BigDecimal multiply(BigDecimal a, BigDecimal b) {
        return computer(3, a, b, POINTS, MODE);
    }

    /**
     * 除
     *
     * @param a
     * @param b
     * @return
     */
    public static BigDecimal divide(BigDecimal a, BigDecimal b) {
        if (b.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.valueOf(0.00);
        }
        return computer(4, a, b, POINTS, MODE);
    }

    public static BigDecimal divide(BigDecimal a, BigDecimal b, int points, RoundingMode mode) {
        return computer(4, a, b, points, mode);
    }

    /**
     * 计算方法
     *
     * @param type   1-加  2-减  3-乘  4-除
     * @param a
     * @param b
     * @param points
     * @param mode
     * @return
     */
    public static BigDecimal computer(int type, BigDecimal a, BigDecimal b, int points, RoundingMode mode) {
        BigDecimal rs;
        switch (type) {
            case 1:
                rs = a.add(b).setScale(points, mode);
                break;
            case 2:
                rs = a.subtract(b).setScale(points, mode);
                break;
            case 3:
                rs = a.multiply(b).setScale(points, mode);
                break;
            default:
                rs = a.divide(b, points, mode);
                break;
        }
        return rs;
    }


    public BigDecimal multiply(BigDecimal a, BigDecimal b, int points, RoundingMode mode) {
        return computer(3, a, b, points, mode);
    }

}

启动类增加注解

@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("cn.tellsea.mapper")

4、搭建订单服务

创建 spring-cloud-alibaba-boot-seata-order 模块,先把第二节的账户服务都操作完成
控制层

package cn.tellsea.controller;

import cn.tellsea.entity.AjaxResult;
import cn.tellsea.entity.BizOrder;
import cn.tellsea.service.IBizOrderService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 

* 订单表 前端控制器 *

* * @author Tellsea * @since 2021-12-31 */
@RestController @RequestMapping("/bizOrder") public class BizOrderController { @Autowired private IBizOrderService orderService; @ApiOperation("创建订单") @GetMapping("/createOrder") public AjaxResult createOrder(BizOrder entity) { return orderService.createOrder(entity); } }

接口层

package cn.tellsea.service;

import cn.tellsea.entity.AjaxResult;
import cn.tellsea.entity.BizOrder;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * 

* 订单表 服务类 *

* * @author Tellsea * @since 2021-12-31 */
public interface IBizOrderService extends IService<BizOrder> { /** * 创建订单 * * @param entity * @return */ AjaxResult createOrder(BizOrder entity); }

接口实现层

package cn.tellsea.service.impl;

import cn.tellsea.entity.AjaxResult;
import cn.tellsea.entity.BizOrder;
import cn.tellsea.feignclient.FeignBizAccountService;
import cn.tellsea.feignclient.FeignBizStorageService;
import cn.tellsea.mapper.BizOrderMapper;
import cn.tellsea.service.IBizOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 

* 订单表 服务实现类 *

* * @author Tellsea * @since 2021-12-31 */
@Slf4j @Service public class BizOrderServiceImpl extends ServiceImpl<BizOrderMapper, BizOrder> implements IBizOrderService { @Autowired private FeignBizAccountService accountService; @Autowired private FeignBizStorageService storageService; @Override public AjaxResult createOrder(BizOrder entity) { log.info("------>开始新建订单"); baseMapper.insert(entity); log.info("------>订单微服务开始调用库存,扣减count"); storageService.decrease(entity.getProductId(), entity.getCount()); log.info("------>订单微服务开始调用库存,扣减end"); log.info("------>订单微服务开始调用账户,扣减money"); accountService.decrease(entity.getUserId(), entity.getMoney()); log.info("------>订单微服务开始调用账户,扣减end"); log.info("------>开始修改订单状态"); entity.setStatus(2); baseMapper.updateById(entity); log.info("------>修改订单状态完毕"); log.info("------>下单完毕"); return AjaxResult.success("下单成功"); } }

启动类增加注解

@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("cn.tellsea.mapper")

增加账户服务远程调用接口

package cn.tellsea.feignclient;

import cn.tellsea.entity.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;

/**
 * 账户服务
 *
 * @author Tellsea
 * @date 2021/12/31
 */
@FeignClient("spring-cloud-alibaba-boot-seata-account")
public interface FeignBizAccountService {

    /**
     * 扣减账户余额
     *
     * @param userId
     * @param money
     * @return
     */
    @PostMapping("/bizAccount/account/decrease")
    AjaxResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

增加库存服务远程调用接口

package cn.tellsea.feignclient;

import cn.tellsea.entity.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 库存服务
 *
 * @author Tellsea
 * @date 2021/12/31
 */
@FeignClient(value = "spring-cloud-alibaba-boot-seata-storage")
public interface FeignBizStorageService {

    /**
     * 扣减库存
     *
     * @param productId
     * @param count
     * @return
     */
    @PostMapping("/bizStorage/storage/decrease")
    AjaxResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}

5、搭建库存服务

创建 spring-cloud-alibaba-boot-seata-storage 模块,先把第二节的账户服务都操作完成
控制层

package cn.tellsea.controller;


import cn.tellsea.entity.AjaxResult;
import cn.tellsea.service.IBizStorageService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 

* 库存表 前端控制器 *

* * @author Tellsea * @since 2021-12-31 */
@RestController @RequestMapping("/bizStorage") public class BizStorageController { @Autowired private IBizStorageService storageService; @ApiOperation("扣减库存") @RequestMapping("/storage/decrease") public AjaxResult decrease(Long productId, Integer count) { return storageService.decrease(productId, count); } }

接口层

package cn.tellsea.service;

import cn.tellsea.entity.AjaxResult;
import cn.tellsea.entity.BizStorage;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * 

* 库存表 服务类 *

* * @author Tellsea * @since 2021-12-31 */
public interface IBizStorageService extends IService<BizStorage> { /** * 扣减库存 * * @param productId * @param count * @return */ AjaxResult decrease(Long productId, Integer count); }

接口实现层

package cn.tellsea.service.impl;

import cn.tellsea.entity.AjaxResult;
import cn.tellsea.entity.BizStorage;
import cn.tellsea.mapper.BizStorageMapper;
import cn.tellsea.service.IBizStorageService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 

* 库存表 服务实现类 *

* * @author Tellsea * @since 2021-12-31 */
@Slf4j @Service public class BizStorageServiceImpl extends ServiceImpl<BizStorageMapper, BizStorage> implements IBizStorageService { @Override public AjaxResult decrease(Long productId, Integer count) { log.info("------->中扣减库存开始"); BizStorage storage = baseMapper.selectById(productId); storage.setUsed(storage.getUsed() + count); storage.setResidue(storage.getResidue() - count); baseMapper.updateById(storage); log.info("------->中扣减库存结束"); return AjaxResult.success("扣减库存成功"); } }

启动类增加注解

@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("cn.tellsea.mapper")

6、测试下单业务

业务需求:下订单 -> 减库存 -> 扣余额 -> 改订单状态

(1)检查服务启动结果

启动三个 服务
【Spring Cloud Alibaba】Seata 分布式事务_第4张图片
检查 Nacos 的服务注册
【Spring Cloud Alibaba】Seata 分布式事务_第5张图片

(2)正常情况下单

数据库请求之前的数据
账户
【Spring Cloud Alibaba】Seata 分布式事务_第6张图片
订单

【Spring Cloud Alibaba】Seata 分布式事务_第7张图片

库存
【Spring Cloud Alibaba】Seata 分布式事务_第8张图片

访问线面连接下单

http://localhost:8008/bizOrder/createOrder?userId=1&productId=1&count=10&money=100

返回结果下单成功
【Spring Cloud Alibaba】Seata 分布式事务_第9张图片
账户:扣减
【Spring Cloud Alibaba】Seata 分布式事务_第10张图片

订单:创建完成
【Spring Cloud Alibaba】Seata 分布式事务_第11张图片

库存:扣减
【Spring Cloud Alibaba】Seata 分布式事务_第12张图片

(3)出错情况下单

在 spring-cloud-alibaba-boot-seata-account 模块的扣减账户余额中增加线程休眠

    @Override
    public AjaxResult decrease(Long userId, BigDecimal money) {
        log.info("------->扣减余额开始");
        //模拟超时异常,全局事务回滚
        //暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        BizAccount account = baseMapper.selectById(userId);
        account.setResidue(BigDecimalUtils.subtract(account.getResidue(), money));
        account.setUsed(BigDecimalUtils.add(account.getUsed(), money));
        baseMapper.updateById(account);
        log.info("------->扣减余额结束");
        return AjaxResult.success("扣减余额成功");
    }

重新访问下单连接

http://localhost:8008/bizOrder/createOrder?userId=1&productId=1&count=10&money=100

报错,因为 OpenFeign 默认调用时限为 1 秒
【Spring Cloud Alibaba】Seata 分布式事务_第13张图片
账户:余额不变

订单:已创建,但未完成
【Spring Cloud Alibaba】Seata 分布式事务_第14张图片
检查商品库存,丢失了!!!
【Spring Cloud Alibaba】Seata 分布式事务_第15张图片

(4)增加 Seata 事物

在创建订单的接口上增加全局事物注解

@GlobalTransactional(name = "tellsea_tx_group", rollbackFor = Exception.class)

重启服务
【Spring Cloud Alibaba】Seata 分布式事务_第16张图片
再次调用

http://localhost:8008/bizOrder/createOrder?userId=1&productId=1&count=10&money=100

同样的报错
【Spring Cloud Alibaba】Seata 分布式事务_第17张图片

账户:未变动
【Spring Cloud Alibaba】Seata 分布式事务_第18张图片

订单:未变动
【Spring Cloud Alibaba】Seata 分布式事务_第19张图片

库存:未变动
【Spring Cloud Alibaba】Seata 分布式事务_第20张图片
到此,Spring Cloud Alibaba Seata 分布式事物,控制成功

7、常见报错

(1)endpoint format should like ip:port

启动服务时,发现报错

2021-03-02 16:22:09.693 ERROR 8384 --- [           main] i.s.c.r.netty.NettyClientChannelManager  : Failed to get available servers: endpoint format should like ip:port

java.lang.IllegalArgumentException: endpoint format should like ip:port
	at io.seata.discovery.registry.FileRegistryServiceImpl.lookup(FileRegistryServiceImpl.java:95) ~[seata-all-1.3.0.jar:1.3.0]
	at io.seata.core.rpc.netty.NettyClientChannelManager.getAvailServerList(NettyClientChannelManager.java:217) ~[seata-all-1.3.0.jar:1.3.0]
	at io.seata.core.rpc.netty.NettyClientChannelManager.reconnect(NettyClientChannelManager.java:162) ~[seata-all-1.3.0.jar:1.3.0]
	at io.seata.core.rpc.netty.RmNettyRemotingClient.registerResource(RmNettyRemotingClient.java:181) [seata-all-1.3.0.jar:1.3.0]
	at io.seata.rm.AbstractResourceManager.registerResource(AbstractResourceManager.java:121) [seata-all-1.3.0.jar:1.3.0]
	at io.seata.rm.datasource.DataSourceManager.registerResource(DataSourceManager.java:146) [seata-all-1.3.0.jar:1.3.0]
	at io.seata.rm.DefaultResourceManager.registerResource(DefaultResourceManager.java:114) [seata-all-1.3.0.jar:1.3.0]

本文使用的 seata 依赖是 spring cloud alibaba seata,所以配置信息应该在 alibaba 下

  • tellsea_tx_group:自定义的组名称,与 vgroupMapping. 的点之后的名称对应
  • seata 版本的不同,vgroupMapping 可能是 vgroup-mapping,这个直接看源码就知道了
错误版本
spring:
  cloud:
	# Seata配置
	seata:
	  tx-service-group: tellsea_tx_group

正确版本
spring:
  cloud:
    alibaba:
      # Seata配置
      seata:
        tx-service-group: tellsea_tx_group

微信公众号

你可能感兴趣的:(Spring,Cloud,Alibaba,分布式,java,spring,cloud)