【Spring Cloud Alibaba】Seata 分布式事务

【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-accountspring-cloud-alibaba-seata-account
spring-cloud-alibaba-boot-seata-orderspring-cloud-alibaba-seata-order
spring-cloud-alibaba-boot-seata-storagespring-cloud-alibaba-seata-storage

2、服务公共内容

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

(1)相关依赖

三个模块的依赖相同

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

(2)application.yml

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

模块端口端点数据库
spring-cloud-alibaba-boot-seata-account80079007alibaba-seata-account
spring-cloud-alibaba-boot-seata-order80089008alibaba-seata-order
spring-cloud-alibaba-boot-seata-storage80099009alibaba-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 目录下
在这里插入图片描述
并在 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

(6)创建模块数据库

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;

/**
 * <p>
 * 账户表 前端控制器
 * </p>
 *
 * @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;

/**
 * <p>
 * 账户表 服务类
 * </p>
 *
 * @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;

/**
 * <p>
 * 账户表 服务实现类
 * </p>
 *
 * @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;

/**
 * <p>
 * 订单表 前端控制器
 * </p>
 *
 * @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;

/**
 * <p>
 * 订单表 服务类
 * </p>
 *
 * @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;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @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;

/**
 * <p>
 * 库存表 前端控制器
 * </p>
 *
 * @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;

/**
 * <p>
 * 库存表 服务类
 * </p>
 *
 * @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;

/**
 * <p>
 * 库存表 服务实现类
 * </p>
 *
 * @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)检查服务启动结果

启动三个 服务

检查 Nacos 的服务注册

(2)正常情况下单

数据库请求之前的数据
账户

订单

库存
在这里插入图片描述

访问线面连接下单

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

返回结果下单成功
在这里插入图片描述
账户:扣减

订单:创建完成

库存:扣减

(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 秒

账户:余额不变

订单:已创建,但未完成

检查商品库存,丢失了!!!

(4)增加 Seata 事物

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

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

重启服务
在这里插入图片描述
再次调用

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

同样的报错

账户:未变动

订单:未变动

库存:未变动

到此,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

微信公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tellsea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值