使用seata解决分布式事物问题

使用Seata实现分布式事物带来的分支事务回滚异常问题 1.场景说明 使用Seata管理2个微服务的数据库添加操作,webSocketFeignService为另外个服务的feign调用 @Autowired private WebSocketFeignService webSocketFeignService; @Transactional @GlobalTransactional @Override ... 阅读详情

seata如果不是很了解的话可以直接到官网查看官方文档说明:http://seata.io/zh-cn/docs/overview/what-is-seata.html

如果你还对SpringBoot、Dubbo、Nacos、Seata、Mybatis 不是很了解的话,这里我为大家整理个它们的官网网站,如下

本例子主要模块为以下版本:
springboot版本:2.2.5.RELEASE
spring-cloud版本:Hoxton.SR3
spring-cloud-alibaba版本:2.2.1.RELEASE
seata版本:1.4.0
nacos版本:1.4.0

如果还没安装好nacos环境的可以参考文档:nacos下载与安装

整个业务逻辑由4个微服务提供支持:

  • 库存服务(storage):扣除给定商品的存储数量。
  • 订单服务(order):根据购买请求创建订单。
  • 帐户服务(account):借记用户帐户的余额。
  • 业务服务(business):处理业务逻辑。
  • 公共模块(common):公共模块。

一、下载并安装seata-server

1,下载seata
https://github.com/seata/seata/releases
在这里插入图片描述
2,解压并移动到 /usr/local/ 目录下

tar -zxvf seata-server-1.4.0.tar.gz
mv seata seata-1.4.0
mv seata-1.4.0/ /usr/local/
cd /usr/local/seata-1.4.0/

3,修改 conf/registry.conf 配置
目前seata支持如下的file、nacos 、apollo、zk、consul的注册中心和配置中心。这里我们以nacos 为例。
将 type 改为 nacos

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "192.168.2.112:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "192.168.2.112:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
  }
}
  • serverAddr = “192.168.2.112:8848” :nacos 的地址
  • namespace = “” :nacos的命名空间默认为``
  • cluster = “default” :集群设置未默认 default

4,修改 config.txt 文件
config.txt 配置文件需要自行下载,下载后放到seata根目录下面,下载地址:https://github.com/seata/seata/blob/develop/script/config-center/config.txt

transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
store.mode=file
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=username
store.db.password=password
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.host=127.0.0.1
store.redis.port=6379
store.redis.maxConn=10
store.redis.minConn=1
store.redis.database=0
store.redis.password=null
store.redis.queryLimit=100
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

配置的详细说明参考官网:http://seata.io/zh-cn/docs/user/configurations.html
这里主要修改了如下几项:

  • store.mode :存储模式 默认file 这里我修改为db 模式 ,并且需要三个表global_table、branch_table和lock_table
  • store.db.driverClassName=com.mysql.jdbc.Driver
  • store.db.db-type=mysql : 存储数据库的类型为mysql
  • store.db.url=jdbc:mysql://192.168.2.112:3306/seata_config?useUnicode=true : 修改为自己的数据库url、port、数据库名称
  • store.db.user=root :数据库的账号
  • store.db.password=123456 :数据库的密码
  • service.vgroupMapping.order-seata-tx-group=default
  • service.vgroupMapping.account-seata-tx-group=default
  • service.vgroupMapping.storage-seata-tx-group=default
  • service.vgroupMapping.business-seata-tx-group=default

注意:如果mysql版本为8.0以上的版本,需要使用8.x以上的驱动包,并且将 store.db.driverClassName=com.mysql.jdbc.Driver 配置改为 store.db.driverClassName=com.mysql.cj.jdbc.Driver
然后直接到seata/lib目录下,将8版本的jar包粘贴到目录下

db模式下的所需的三个表的数据库脚本下载地址:https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql

5、将 Seata 配置添加到 Nacos 中
脚本下载地址:https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
下载后将脚本放到 seata bin/ 目录下,并将脚本权限改成具有可执行权限

cd bin
chmod 755 nacos-config.sh

执行 nacos-config.sh 脚本,将配置上传到nacos

sh ./nacos-config.sh

看到输出如下内容,说明脚本已经执行成功
在这里插入图片描述
登陆到nacos控制台也能够看到上传的配置内容
在这里插入图片描述
6、使用db模式启动 seata

sh seata-server.sh -h 192.168.2.112 -p 8091 -m db
Options:
    --host, -h
      The host to bind,主机绑定ip,nacos的注册地址
      Default: 0.0.0.0
    --port, -p
      The port to listen.
      Default: 8091
    --storeMode, -m
      log store mode : file、db
      Default: file
    --help

Server端存储模式(store.mode)现有file、db、redis三种,file模式无需改动,直接启动即可,下面专门讲下db和redis启动步骤。
注: file模式为单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高;

db模式为高可用模式,全局事务会话信息通过db共享,相应性能差些;

redis模式Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置合适当前场景的redis持久化配置.

二、代码构建

项目地址:https://gitee.com/hwm0717/springcloud_seata_demo
4个微服务提供支持:

  • 库存服务(storage):扣除给定商品的存储数量。
  • 订单服务(order):根据购买请求创建订单。
  • 帐户服务(account):借记用户帐户的余额。
  • 业务服务(business):处理业务逻辑。
  • 公共模块(common):公共模块。

我们只需要在业务处理的方法handleBusiness添加一个注解 @GlobalTransactional 即可实现分布式事物的处理

请求逻辑架构:
在这里插入图片描述
pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>seata.demo</groupId>
    <artifactId>springcloud_seata_demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <springboot.version>2.2.5.RELEASE</springboot.version>
        <mysql.connector.java.version>8.0.21</mysql.connector.java.version>
        <druid-spring-boot-starter.version>1.1.22</druid-spring-boot-starter.version>

        <seata.version>1.4.0</seata.version>

        <!-- Spring Settings -->
        <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <modules>
        <module>business</module>
        <module>account</module>
        <module>order</module>
        <module>storage</module>
        <module>common</module>
    </modules>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

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

        <!--  springboot 整合web组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!-- mybatisplus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.0</version>
        </dependency>

        <!-- mybatis分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>

        <!--引入数据库连接 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.connector.java.version}</version>
        </dependency>

        <!-- 使用 druid数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid-spring-boot-starter.version}</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!-- swagger整合:https://github.com/SpringForAll/spring-boot-starter-swagger -->
        <dependency>
            <groupId>com.spring4all</groupId>
            <artifactId>swagger-spring-boot-starter</artifactId>
            <version>1.7.0.RELEASE</version>
        </dependency>

        <!-- 项目中引入seata依赖 begin -->
        <!-- 如果你的微服务是dubbo引入依赖 -->
<!--        <dependency>-->
<!--            <groupId>io.seata</groupId>-->
<!--            <artifactId>seata-spring-boot-starter</artifactId>-->
<!--            <version>${seata.version}</version>-->
<!--        </dependency>-->

        <!-- 如果你是springcloud:-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <version>2.2.0.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>${seata.version}</version>
        </dependency>
        <!-- 项目中引入seata依赖 end -->

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${springboot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

1,库存服务:

@Data
public class TStorage {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String commodityCode;
    private String name;
    private Integer count;

}
public interface TStorageMapper extends BaseMapper<TStorage> {

    /**
     * 扣减商品库存
     * @Param: commodityCode 商品code  count扣减数量
     * @Return:
     */
    int decreaseStorage(@Param("commodityCode") String commodityCode, @Param("count") Integer count);
}
@RestController
@RequestMapping("/storage")
@Slf4j
public class StorageServiceImpl extends ServiceImpl<TStorageMapper, TStorage> {

    @PostMapping("/decreaseStorage")
    @GlobalTransactional
    @Transactional
    public ObjectResponse decreaseStorage(@RequestBody CommodityDTO commodityDTO) {

        log.info("seata全局xid={}", RootContext.getXID());

        int storage = baseMapper.decreaseStorage(commodityDTO.getCommodityCode(), commodityDTO.getCount());

        ObjectResponse<Object> response = new ObjectResponse<>();
        if (storage > 0) {
            response.setStatus(RspStatusEnum.SUCCESS.getCode());
            response.setMessage(RspStatusEnum.SUCCESS.getMessage());
            return response;
        }

        response.setStatus(RspStatusEnum.FAIL.getCode());
        response.setMessage(RspStatusEnum.FAIL.getMessage());
        return response;
    }

}
@SpringBootApplication
@EnableFeignClients
@MapperScan("seata.demo.storage.mappers")
public class StorageApplication {

    public static void main(String[] args) {
        SpringApplication.run(StorageApplication.class, args);
    }

}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="seata.demo.storage.mappers.TStorageMapper">

    <update id="decreaseStorage">
        update t_storage set count = count-${count} where commodity_code = #{commodityCode}
    </update>

</mapper>

2,订单服务

@Data
public class TOrder {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String orderNo;
    private String userId;
    private String commodityCode;
    private Integer count;
    private Double amount;

}

public interface TOrderMapper extends BaseMapper<TOrder> {

    /**
     * 创建订单
     * @Param:  order 订单信息
     * @Return:
     */
    void createOrder(@Param("order") TOrder order);
}

@RestController
@RequestMapping("/order")
@Slf4j
public class OrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> {

    @Autowired
    private AccountServiceFeign accountServiceFeign;

    @PostMapping("/createOrder")
    @GlobalTransactional
    @Transactional
    public ObjectResponse<OrderDTO> createOrder(@RequestBody OrderDTO orderDTO) {

        log.info("seata全局xid={}", RootContext.getXID());

        ObjectResponse<OrderDTO> response = new ObjectResponse<>();
        //扣减用户账户
        AccountDTO accountDTO = new AccountDTO();
        accountDTO.setUserId(orderDTO.getUserId());
        accountDTO.setAmount(orderDTO.getOrderAmount());
        ObjectResponse objectResponse = accountServiceFeign.decreaseAccount(accountDTO);

        //生成订单号
        orderDTO.setOrderNo(UUID.randomUUID().toString().replace("-",""));
        //生成订单
        TOrder tOrder = new TOrder();
        BeanUtils.copyProperties(orderDTO,tOrder);
        tOrder.setCount(orderDTO.getOrderCount());
        tOrder.setAmount(orderDTO.getOrderAmount().doubleValue());
        try {
            baseMapper.createOrder(tOrder);
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(RspStatusEnum.FAIL.getCode());
            response.setMessage(RspStatusEnum.FAIL.getMessage());
            return response;
        }

        if (objectResponse.getStatus() != 200) {
            response.setStatus(RspStatusEnum.FAIL.getCode());
            response.setMessage(RspStatusEnum.FAIL.getMessage());
            return response;
        }

        response.setStatus(RspStatusEnum.SUCCESS.getCode());
        response.setMessage(RspStatusEnum.SUCCESS.getMessage());
        return response;
    }

}
@SpringBootApplication
@EnableFeignClients
@MapperScan("seata.demo.order.mappers")
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="seata.demo.order.mappers.TOrderMapper">

    <!--创建订单-->
    <insert id="createOrder" parameterType="seata.demo.order.entity.TOrder">
      insert into t_order values(null,#{order.orderNo},#{order.userId},#{order.commodityCode},${order.count},${order.amount})
    </insert>

</mapper>

3,账户服务

@Data
public class TAccount {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String userId;
    private Double amount;

}
public interface TAccountMapper extends BaseMapper<TAccount> {

    /**
     * 减少账户余额
     * @param userId
     * @param amount
     * @return
     */
    int decreaseAccount(@Param("userId") String userId, @Param("amount") Double amount);
}

@RestController
@RequestMapping("/account")
@Slf4j
public class AccountServiceImpl extends ServiceImpl<TAccountMapper, TAccount> {

    @PostMapping("/decreaseAccount")
    @GlobalTransactional
    @Transactional
    public ObjectResponse decreaseAccount(@RequestBody AccountDTO accountDTO) {

        log.info("seata全局xid={}", RootContext.getXID());

        int account = baseMapper.decreaseAccount(accountDTO.getUserId(), accountDTO.getAmount().doubleValue());
        ObjectResponse<Object> response = new ObjectResponse<>();
        if (account > 0) {
            response.setStatus(RspStatusEnum.SUCCESS.getCode());
            response.setMessage(RspStatusEnum.SUCCESS.getMessage());
            return response;
        }

        response.setStatus(RspStatusEnum.FAIL.getCode());
        response.setMessage(RspStatusEnum.FAIL.getMessage());
        return response;
    }
}
@SpringBootApplication
@EnableFeignClients
@MapperScan("seata.demo.account.mappers")
public class AccountApplication {

    public static void main(String[] args) {
        SpringApplication.run(AccountApplication.class, args);
    }

}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="seata.demo.account.mappers.TAccountMapper">

    <update id="decreaseAccount">
      update t_account set amount = amount-${amount} where user_id = #{userId}
    </update>

</mapper>

4,业务服务

@RestController
@RequestMapping("/business")
@Slf4j
public class BusinessServiceImpl {

    @Autowired
    private StorageServiceFeign storageServiceFeign;
    @Autowired
    private OrderServiceFeign orderServiceFeign;

    /**
     * 处理业务逻辑 正常的业务逻辑
     *
     * @Param:
     * @Return:
     */
    @GlobalTransactional
    @PostMapping("/handleBusiness")
    @ApiOperation("处理业务逻辑 正常的业务逻辑")
    public ObjectResponse handleBusiness(@RequestBody BusinessDTO businessDTO) {
        log.info("开始全局事务,XID = " + RootContext.getXID());

        ObjectResponse<Object> objectResponse = new ObjectResponse<>();
        //1、扣减库存
        CommodityDTO commodityDTO = new CommodityDTO();
        commodityDTO.setCommodityCode(businessDTO.getCommodityCode());
        commodityDTO.setCount(businessDTO.getCount());
        ObjectResponse storageResponse = storageServiceFeign.decreaseStorage(commodityDTO);
        //2、创建订单
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setUserId(businessDTO.getUserId());
        orderDTO.setCommodityCode(businessDTO.getCommodityCode());
        orderDTO.setOrderCount(businessDTO.getCount());
        orderDTO.setOrderAmount(businessDTO.getAmount());
        ObjectResponse<OrderDTO> response = orderServiceFeign.createOrder(orderDTO);

        if (storageResponse.getStatus() != 200 || response.getStatus() != 200) {
            throw new DefaultException(RspStatusEnum.FAIL);
        }

        objectResponse.setStatus(RspStatusEnum.SUCCESS.getCode());
        objectResponse.setMessage(RspStatusEnum.SUCCESS.getMessage());
        objectResponse.setData(response.getData());
        return objectResponse;
    }

    /**
     * 出处理业务服务,出现异常回顾
     *
     * @param businessDTO
     * @return
     */
    @GlobalTransactional
    @PostMapping("/handleBusiness2")
    @ApiOperation("出处理业务服务,出现异常回顾")
    @Transactional
    public ObjectResponse handleBusiness2(@RequestBody BusinessDTO businessDTO) {
        log.info("开始全局事务,XID = " + RootContext.getXID());
        ObjectResponse<Object> objectResponse = new ObjectResponse<>();
        //1、扣减库存
        CommodityDTO commodityDTO = new CommodityDTO();
        commodityDTO.setCommodityCode(businessDTO.getCommodityCode());
        commodityDTO.setCount(businessDTO.getCount());
        ObjectResponse storageResponse = storageServiceFeign.decreaseStorage(commodityDTO);
        //2、创建订单
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setUserId(businessDTO.getUserId());
        orderDTO.setCommodityCode(businessDTO.getCommodityCode());
        orderDTO.setOrderCount(businessDTO.getCount());
        orderDTO.setOrderAmount(businessDTO.getAmount());
        ObjectResponse<OrderDTO> response = orderServiceFeign.createOrder(orderDTO);

//        打开注释测试事务发生异常后,全局回滚功能
        if (true) {
            throw new RuntimeException("测试抛异常后,分布式事务回滚!");
        }

        if (storageResponse.getStatus() != 200 || response.getStatus() != 200) {
            throw new DefaultException(RspStatusEnum.FAIL);
        }

        objectResponse.setStatus(RspStatusEnum.SUCCESS.getCode());
        objectResponse.setMessage(RspStatusEnum.SUCCESS.getMessage());
        objectResponse.setData(response.getData());
        return objectResponse;
    }

}
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableSwagger2Doc
@EnableFeignClients
public class BusinessApplication {

    public static void main(String[] args) {
        SpringApplication.run(BusinessApplication.class, args);
    }

}

5,公共模块

@Data
public class AccountDTO implements Serializable {

    private Integer id;

    private String userId;

    private BigDecimal amount;
}

@Data
public class BusinessDTO implements Serializable {

    private String userId;

    private String commodityCode;

    private String name;

    private Integer count;

    private BigDecimal amount;

}
@Data
public class CommodityDTO implements Serializable {

    private Integer id;

    private String commodityCode;

    private String name;

    private Integer count;
}
@Data
public class OrderDTO implements Serializable {

    private String orderNo;

    private String userId;

    private String commodityCode;

    private Integer orderCount;

    private BigDecimal orderAmount;

}
public enum RspStatusEnum {
    /**
     * SUCCESS
     */
    SUCCESS(200,"成功"),
    /**
     * Fail rsp status enum.
     */
    FAIL(999,"失败"),
    /**
     * Exception rsp status enum.
     */
    EXCEPTION(500,"系统异常");

    private int code;

    private String message;

    RspStatusEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    /**
     * Gets code.
     *
     * @return the code
     */
    public int getCode() {
        return code;
    }

    /**
     * Gets message.
     *
     * @return the message
     */
    public String getMessage() {
        return message;
    }
}

public class DefaultException extends RuntimeException{

    private RspStatusEnum rspStatusEnum;

    public DefaultException(String message, Throwable cause) {
        super(message, cause);
    }

    public DefaultException(RspStatusEnum rspStatusEnum) {
        super(rspStatusEnum.getMessage());
        this.rspStatusEnum = rspStatusEnum;
    }

    public DefaultException(RspStatusEnum rspStatusEnum, Throwable cause) {
        super(rspStatusEnum.getMessage(), cause);
        this.rspStatusEnum = rspStatusEnum;
    }

    public RspStatusEnum getRspStatusEnum() {
        return rspStatusEnum;
    }

    public void setRspStatusEnum(RspStatusEnum rspStatusEnum) {
        this.rspStatusEnum = rspStatusEnum;
    }
}

@FeignClient(value = "account", path = "/account")
public interface AccountServiceFeign {

    /**
     * 从账户扣钱
     */
    @ApiOperation("从账户扣钱")
    @PostMapping("/decreaseAccount")
    ObjectResponse decreaseAccount(@RequestBody AccountDTO accountDTO);
}

@FeignClient(value = "order", path = "/order")
public interface OrderServiceFeign {

    /**
     * 创建订单
     */
    @ApiOperation("创建订单")
    @PostMapping("/createOrder")
    ObjectResponse<OrderDTO> createOrder(@RequestBody OrderDTO orderDTO);
}

@FeignClient(value = "storage", path = "/storage")
public interface StorageServiceFeign {

    /**
     * 扣减库存
     */
    @ApiOperation("扣减库存")
    @PostMapping("/decreaseStorage")
    ObjectResponse decreaseStorage(@RequestBody CommodityDTO commodityDTO);
}

@Data
public class BaseResponse implements Serializable {

    private int status = 200;

    private String message;
}

@Data
public class ObjectResponse<T> extends BaseResponse implements Serializable {

    private T data;

}

6,配置文件
主要三个配置文件,每个应用里面复制一份,内容基本一致,只需要根据自己配置改下扫描的包和应用名称即可

application-global.properties


#服务端口号
server.port=8080

spring.profiles.include=global,seata

#服务名称
spring.application.name=business

#nacos
spring.cloud.nacos.discovery.server-addr=192.168.2.112:8848

#日志配置
logging.level.root=info
logging.file.path=C:\\www\\logs\\${spring.application.name}

#数据库连接信息
spring.datasource.url=jdbc:mysql://192.168.2.112:3306/springcloud_seata_demo?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=123456

#swagger
swagger.enabled=true
swagger.title=业务逻辑处理
swagger.description=业务逻辑处理
swagger.version=1.0.0
swagger.base-package=seata.demo.business.service

#设置feign超时时间,解决第一次调用时超时问题
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000

application-seata.yml

#====================================Seata Config===============================================
seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: ${spring.application.name}-seata-tx-group # 事务群组(可以每个应用独立取名,也可以使用相同的名字)
  registry:
    file:
      name: file.conf
    type: nacos
    nacos:
      server-addr: 192.168.2.112:8848
      namespace:
      cluster: default
  config:
    file:
      name: file.conf
    type: nacos
    nacos:
      namespace:
      server-addr: 192.168.2.112:8848

application.properties


#服务端口号
server.port=8080

#引入外部配置文件
spring.profiles.include=global,seata

#服务名称
spring.application.name=business

#nacos
spring.cloud.nacos.discovery.server-addr=192.168.2.112:8848

#日志配置
logging.level.root=info
logging.file.path=C:\\www\\logs\\${spring.application.name}

#数据库连接信息
spring.datasource.url=jdbc:mysql://192.168.2.112:3306/springcloud_seata_demo?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=123456

#swagger
swagger.enabled=true
swagger.title=业务逻辑处理
swagger.description=业务逻辑处理
swagger.version=1.0.0
swagger.base-package=seata.demo.business.service

#设置feign超时时间,解决第一次调用时超时问题
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000

7,准备数据库
注意: MySQL必须使用InnoDB engine.
创建数据库 并导入数据库脚本
这里为了简化我将这个三张表创建到一个库中,使用是三个数据源来实现。

DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `amount` double(14,2) DEFAULT '0.00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_account
-- ----------------------------
INSERT INTO `t_account` VALUES ('1', '1', '4000.00');

-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_no` varchar(255) DEFAULT NULL,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT '0',
  `amount` double(14,2) DEFAULT '0.00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_order
-- ----------------------------

-- ----------------------------
-- Table structure for t_storage
-- ----------------------------
DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_storage
-- ----------------------------
INSERT INTO `t_storage` VALUES ('1', 'C201901140001', '水杯', '1000');

-- ----------------------------
-- Table structure for undo_log
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of undo_log
-- ----------------------------
SET FOREIGN_KEY_CHECKS=1;

三、测试

使用swagger发送一个下单请求,调用 /business/handleBusiness 方法:http://localhost:8080/swagger-ui.html#/business-service-impl/handleBusinessUsingPOST
参数内容:

{
  "amount": 100,
  "commodityCode": "C201901140001",
  "count": 100,
  "name": "水杯",
  "userId": "1"
}

返回结果:

{
  "status": 200,
  "message": "成功",
  "data": null
}

在这里插入图片描述

控制台打印
在这里插入图片描述

在这里插入图片描述
事物提交成功,查看数据变化
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
数据没有问题,全部按照正常流程提交了

2,测试数据回滚:
参数一样,通过swagger调用 /business/handleBusiness2 异常回滚方法
返回结果:
在这里插入图片描述
控制台输出
在这里插入图片描述
在这里插入图片描述
再次查看数据库数据,已经回滚,和上面的数据一致。

分布式事物seata-demo测试 springboot测试分布式事物seata 文章目录springboot测试分布式事物seata1.下载代码2.修改数据库和nacso地址3.启动代码测试 1.下载代码 seata服务端:https://github.com/seata/seata seata示例demo:https://github.com/seata/seata-samples Quick Start:https://git... 阅读详情

相关推荐

05、基于ALV表报实现复选框、全选、取消全选等功能

基于Function ALV实现复选框、全选及取消全选等功能

wo520yl1314的博客 2370

Seata-Server分布式事务原理加源码 (五) - Seata配置Nacos注册中心和配置中心

Seata支持注册服务到Nacos,以及支持Seata所有配置放到Nacos配置中心,在Nacos中统一维护;高可用模式下就需要配合Nacos来完成[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vFIJcTr-1676371405632)(image-20220118234555417.png)]

持续学习,持续更新 477

【ANSYS Workbench仿真】命令流:压力渗透pressure penetration

对与流体接触的part添加边界压力,使流体压力渗透点的接触应力与流体压力相等,以便得到真实的变形结果,就需要用到“pressure penetration”。 但是在ansys中,只能通过ADPL的形式添加,即在workbench中添加命令流的形式。此文主要介绍了**压力渗透的添加要点**和**命令流使用说明**。

路边小M's Blog 1万+

微服务: Seata AT 分布式事务配置出现异常解决(相当全面)(下篇)

异常分类如下所示: 1. no available service 'null' found, please make sure registry *** 2.Could not create connection to database 3.no available service 'default' found 4.Buffer pool was not set on *, the default pool will be... 5. seata at undo_log数据不删除解决方案

pingzhuyan的博客 2418

微处理器实验报告-实验四

实验四 可编程定时/计数器8253实验 一、实验要求 利用8086外接8253可编程定时/计数器,可以实现方波的产生。 二、实验目的 1、学习8086与8253的连接方法。 2、学习8253的控制方法。 3、掌握8253定时器/计数器的工作方式和编程原理

青竹小轩的博客 5110

嵌入式Linux开发:手把手教你调试OV9281摄像头Sensor(基于全志平台DTS配置)

本文详细解析了嵌入式Linux开发中OV9281摄像头Sensor的调试全流程,重点介绍了基于全志平台的DTS配置指南。从硬件信号检测到软件驱动配置,包括设备树关键参数设置、内核驱动适配实战及图像质量调优技巧,为开发者提供了一套完整的调试方案,助力工业视觉和边缘计算设备的开发。

weixin_28716769的博客 371

seata配置详情

seata 配置,以及遇到问题解决

Xiaobai_0106的博客 1657

SQL注入绕过information_schema:实战技法与替代路径详解

SQL注入是Web安全领域的核心漏洞类型,其原理是通过构造恶意SQL语句,干扰应用程序与数据库的正常交互逻辑,从而窃取或篡改数据。在渗透测试与CTF竞赛中,绕过常见防护措施是提升技术能力的关键。当标准的信息收集方式受阻时,例如无法访问information_schema数据库,攻击者需要掌握替代的元数据获取方法。这些技术价值在于模拟真实环境中日益严密的防御体系,锻炼在受限条件下的系统性信息收集思维。典型的应用场景包括存在WAF过滤、权限限制或编码干扰的实战环境。本文将围绕SQL注入这一核心,深入探讨如何利用

weixin_33980459的博客 427

【Seata】Seata配置上传Nacos

seata向nacos上传配置,将所有配置项整合为一个配置文件

Syals的博客 1369

OpenHarmony WebView引擎:从Chromium魔改到分布式渲染的终极解析

ArkWeb框架提供了强大的Web应用开发能力,但同时也需要注意网络安全问题,尤其是跨域请求方面。

m0_67505153的博客 1194

分布式计算Ray框架面试题及参考答案

动态任务图执行引擎是一种能够根据任务之间的依赖关系和运行时的实际情况,动态构建和执行任务图的系统。在 Ray 中,动态任务图执行引擎允许任务在运行时根据数据可用性、计算资源状态等因素,灵活地决定后续任务的执行顺序和方式。对于 AI 应用来说,其往往具有动态性的特点。例如,在深度学习模型的训练过程中,可能需要根据训练的结果动态调整下一轮训练的参数、数据增强方式或者模型结构。动态任务图执行引擎可以很好地支持这些动态需求。它能够根据 AI 应用中各个任务的实时输出和依赖关系,动态地生成和调整任务图。

大模型大数据攻城狮的专栏 1349

分布式事务seata应用

Seata文档配置 1、简要说明 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。 一阶段:事务协调者通知每一个服务处理本地事务,每个服务开始处理但是不会提交事务,处理完毕后告知协调者。 二阶段:协调者收到所有服务的消息后通知他们提交事务。 Seata的工作机制: 一阶段:先解析SQL生成前后镜像,插入回滚日志,在提交事务前,向协调者申请全局锁后,然后提交本地事务,将提交结果发送给协调者。 二阶段-回滚:收到协调者的回滚请求,执行逆向SQL。 二阶段-提交:收到协调者

weixin_43877856的博客 1044

Linux驱动学习--HCI框架及源码分析

目录 一、引言 二、HCI框架 三、pcm_read简要分析 一、引言 最近一直在分析蓝牙驱动,发现在底层驱动与上层协议栈间还有一层HCI,主机控制层,两者之间通过HCI协议通讯 而Linux也将HCI协议抽象层了一个框架。 为上层提供一个统一的接口,让上层协议不依赖于具体硬件的实现。HCI在硬件中的固件与HCI在主机端的驱动通信方式有多种,比如像 UART、USB和PC Card等等。hci_core.c相当于一个框架,用于把各种具体通信方式绞合起来,并提供一些公共函数的实现。 今天就来简单的分析一下 二

文艺小少年的博客 2002

Seata分布式事物(四)解决RPC+feign跨服务调用提供者无XID无法回滚问题

事务传播 Seata 全局事务的传播机制就是指事务上下文的传播,根本上,就是 XID 的应用运行时的传播方式。 跨服务调用场景下的事务传播,本质上就是要把 XID 通过服务调用传递到服务提供方,并绑定到 RootContext 中去。 只要能做到这点,理论上 Seata 可以支持任意的微服务框架。 只要是同一个请求链路,可以将XID作为传输参数传递给服务提供者 业务方法中获取XID,如下示例 调用者 @RequestMapping(value = "testDataApi") @GlobalTr

CodingAnHour 2456

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

SpringCloud Alibaba 使用Seata解决分布式事物

一、Seata Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。 seata提供了AT、TCC、及SAGA(长事务)、XA等模式。是将各分布式分支事务统一为全局事务,通过全局事务的成功与否判断是否需要回滚提交各分支事务。 seata架构中有三大组件: - TC(Transaction Coordinator)事务协调器,维护全局事务的运行状态,负责协调并驱动全局

小毕超博客 4867

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

一站式分布式事物解决方案 seata

之前我们聊完了分布式框架spring cloud,今天我们来聊下由分布式应用带来的问题,分布式事物。 什么是分布式事物,这里摘录一段分布式事物定义: 提起分布式事务,最早指的涉及多个资源的数据库事务问题。 wiki对分布式事务的定义: Adistributed transaction is a database transaction in which two or more ne...

ethan的专栏 1067

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

springCloudAlibaba集成seata实战(分布式事物详解)

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、 和 XA 事务模式,为用户打造一站式的分布式解决方案。仓储服务:对给定的商品进行增删操作记录数量订单服务:根据采购者的需求创建订单账户服务:从用户账户中扣除余额、积分等在这套架构中,用户下单购买商品的业务,就需要三个服务来完成,每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题就没办法保证,Seata就是来进行解决这种问题的解决方案。

javaeEEse的博客 7086

Seata分布式事物案例及详解

官网: https://seata.apache.org/zh-cn/download/seata-server 下载后直接解压 进到bin目录 输入cmd 启动命令: 进入localhost:7091 默认账户跟密码都为seata 导入依赖 创建四个微服务项目 启动类 实体类 控制层 持久层 业务层 配置application.yml 配置seata地址 启动类 控制层 远程调用 业务层 配置application.yml 配置seata地址 启动类 实体类 控制层 业

qq_62374596的博客 1835

seata集群部署_Seata分布式事物使用

1.背景在微服务框架下,有些操作需要依赖不同的服务,操作不同的数据库,为了保证操作的完整性,我们需要分布式事物。Seata 是阿里巴巴开源的一款分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。本文介绍AT和TCC事物模式的使用。2.准备工作2.1Naco...

weixin_39725118的博客 1388

分布式事物seata的AT模式实战

Seata是一款开源的分布式事务解决方案,提供AT、TCC、SAGA和XA四种事务模式。其核心架构包含三个角色:事务协调者(TC)负责全局事务状态管理,事务管理器(TM)定义事务范围,资源管理器(RM)处理分支事务。AT模式采用两阶段提交:准备阶段执行SQL并生成反向SQL日志,提交阶段确认执行或通过反向SQL回滚。实践部分演示了基于Docker部署MySQL和Nacos环境,并通过示例项目展示了订单创建和库存减少的分布式事务处理过程,包括正常执行和异常回滚场景。

每天学习一点点 1015

分布式事物框架seata使用示例

随着微服务使用越来越广泛,微服务的管理也成为了热点的技术话题,微服务化后带来最主要的技术问题就是分布式事物,Seata 作为一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务,我们通过demo来演示seata的使用 一、seata基本原理 Seata中的相关概念包括: 1)Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。 ...

u012477420的博客 1172

springCloud整合Seata实现分布式事物

已经很久没有进行技术点总结了,今天有时间整理分享分布式事物相关内容一下。。。。 本文以SpringCloud Alibaba + Nacos微服务框架作为项目演示: 参考博客:https://blog.youkuaiyun.com/thinkingcao/category_9716788.html 一、分布式事务解决方案 https://blog.youkuaiyun.com/Thinkingcao/article/details/109320174 二、seata简介 https://blog.youkuaiyun.com/Th

qq_15901351的博客 2207

seata 集群_Seata分布式事物使用

1.背景在微服务框架下,有些操作需要依赖不同的服务,操作不同的数据库,为了保证操作的完整性,我们需要分布式事物。Seata 是阿里巴巴开源的一款分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。本文介绍AT和TCC事物模式的使用。2.准备工作2.1Naco...

weixin_39575047的博客 590

分布式事物seata篇(搭建使用以及理解)

什么是seata Seata(Simple Extensible Autonomous Transaction Architecture) 是阿里巴巴一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。 Seata搭建使用 (1)服务端 ①下载seata服务(启动包): 首先访问:https://github.com/seata/seata/releases 下载我们需要使用的seata1.2.0服务 https://github.com/seata/seata/releases/do

banzhi_qingnian的博客 3734

springcloud nacos seata 分布式事物实战

一、准备工作 seata-server-1.3.0 https://github.com/seata/seata/releases nacos-server-1.1.:https://github.com/alibaba/nacos/releases/tag/1.1.3 demo 工程代码:https://github.com/leo20131231/seata_demo/tree/master/springcloud-nacos-seata 4、建数据库 ...

lrylrq1991的专栏 1952

分布式事物解决方案-Seata

分布式事物解决方案

m0_38068812的博客 205
上一篇: linux安装并启动nacos
下一篇: 发生异常时,从ExceptionHandler中获取Body参数进行分析
hwmhehe
博客等级 码龄13年 86粉丝 106原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值