概述

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
中文开发文档http://seata.io/zh-cn/
Github地址:https://github.com/seata/seata

Seata术语

TC (Transaction Coordinator) - 事务协调者 维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

环境准备

二进制启动

release版本下载地址https://github.com/seata/seata/releases

  1. #!/bin/bash
  2. wget https://handson.oss-cn-shanghai.aliyuncs.com/seata-server-1.4.1.zip -O .seata-server-1.4.1.zip
  3. unzip .seata-server-1.4.1.zip
  4. nohup sh ~/seata/bin/seata-server.sh -p 8091 -m file &

docker

dockerhub地址https://hub.docker.com/r/seataio/seata-server
快速创建方式:

  1. docker run -d --name seata-server -p 8091:8091 seataio/seata-server:1.4.1

根pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.alibaba.seata</groupId>
  6. <artifactId>seata-sample</artifactId>
  7. <packaging>pom</packaging>
  8. <version>0.0.1-SNAPSHOT</version>
  9. <modules>
  10. <module>business-service</module>
  11. <module>storage-service</module>
  12. <module>order-service</module>
  13. <module>common-service</module>
  14. </modules>
  15. <name>seata-sample</name>
  16. <description>seata sample project for Spring Boot</description>
  17. <properties>
  18. <java.version>1.8</java.version>
  19. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  20. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  21. <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
  22. <alibaba.cloud.version>2.2.3.RELEASE</alibaba.cloud.version>
  23. <seata.version>1.4.0</seata.version>
  24. <mysql.version>8.0.11</mysql.version>
  25. <lombok.version>1.16.4</lombok.version>
  26. </properties>
  27. <dependencies>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-test</artifactId>
  35. <scope>test</scope>
  36. <exclusions>
  37. <exclusion>
  38. <groupId>org.junit.vintage</groupId>
  39. <artifactId>junit-vintage-engine</artifactId>
  40. </exclusion>
  41. </exclusions>
  42. </dependency>
  43. </dependencies>
  44. <dependencyManagement>
  45. <dependencies>
  46. <dependency>
  47. <groupId>org.springframework.boot</groupId>
  48. <artifactId>spring-boot-dependencies</artifactId>
  49. <version>${spring-boot.version}</version>
  50. <type>pom</type>
  51. <scope>import</scope>
  52. </dependency>
  53. <dependency>
  54. <groupId>com.alibaba.cloud</groupId>
  55. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  56. <version>${alibaba.cloud.version}</version>
  57. <type>pom</type>
  58. <scope>import</scope>
  59. </dependency>
  60. <dependency>
  61. <groupId>org.springframework.cloud</groupId>
  62. <artifactId>spring-cloud-starter-openfeign</artifactId>
  63. <version>${alibaba.cloud.version}</version>
  64. </dependency>
  65. <dependency>
  66. <groupId>org.springframework.cloud</groupId>
  67. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  68. <version>${alibaba.cloud.version}</version>
  69. </dependency>
  70. <dependency>
  71. <groupId>io.seata</groupId>
  72. <artifactId>seata-spring-boot-starter</artifactId>
  73. <version>${seata.version}</version>
  74. </dependency>
  75. <dependency>
  76. <groupId>mysql</groupId>
  77. <artifactId>mysql-connector-java</artifactId>
  78. <version>${mysql.version}</version>
  79. </dependency>
  80. <dependency>
  81. <groupId>org.projectlombok</groupId>
  82. <artifactId>lombok</artifactId>
  83. <version>${lombok.version}</version>
  84. </dependency>
  85. <dependency>
  86. <groupId>org.mybatis.spring.boot</groupId>
  87. <artifactId>mybatis-spring-boot-starter</artifactId>
  88. <version>2.1.0</version>
  89. </dependency>
  90. </dependencies>
  91. </dependencyManagement>
  92. <build>
  93. <plugins>
  94. <plugin>
  95. <groupId>org.apache.maven.plugins</groupId>
  96. <artifactId>maven-compiler-plugin</artifactId>
  97. <version>3.8.1</version>
  98. <configuration>
  99. <source>1.8</source>
  100. <target>1.8</target>
  101. <encoding>UTF-8</encoding>
  102. </configuration>
  103. </plugin>
  104. <plugin>
  105. <groupId>org.springframework.boot</groupId>
  106. <artifactId>spring-boot-maven-plugin</artifactId>
  107. <version>2.3.0.RELEASE</version>
  108. </plugin>
  109. </plugins>
  110. </build>
  111. </project>

工程说明

common-service

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>seata-sample</artifactId>
  7. <groupId>com.alibaba.seata</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>common-service</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.projectlombok</groupId>
  15. <artifactId>lombok</artifactId>
  16. </dependency>
  17. </dependencies>
  18. </project>

常量定义

  1. package com.alibaba.seata.sample.common;
  2. /**
  3. * @author slievrly
  4. */
  5. public interface Constants {
  6. String SUCCESS_RESPONSE = "ok";
  7. String FAIL_RESPONSE = "fail";
  8. }

实体类 Account

  1. package com.alibaba.seata.sample.common.entity;
  2. import lombok.Data;
  3. /**
  4. * @author slievrly
  5. */
  6. @Data
  7. public class Account {
  8. private Integer id;
  9. private String userId;
  10. private Integer money;
  11. }

实体类 订单Order

  1. package com.alibaba.seata.sample.common.entity;
  2. import lombok.Data;
  3. /**
  4. * @author slievrly
  5. */
  6. @Data
  7. public class Order {
  8. private Integer id;
  9. private String userId;
  10. private String commodityCode;
  11. private Integer count;
  12. private Integer money;
  13. }

Storage

  1. package com.alibaba.seata.sample.common.entity;
  2. import lombok.Data;
  3. /**
  4. * @author slievrly
  5. */
  6. @Data
  7. public class Storage {
  8. private Integer id;
  9. private String commodityCode;
  10. private Integer count;
  11. }

business-service

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>seata-sample</artifactId>
  7. <groupId>com.alibaba.seata</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>business-service</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>${project.groupId}</groupId>
  15. <artifactId>common-service</artifactId>
  16. <version>${project.version}</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-openfeign</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.cloud</groupId>
  24. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>io.seata</groupId>
  28. <artifactId>seata-spring-boot-starter</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>mysql</groupId>
  32. <artifactId>mysql-connector-java</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>com.alibaba.cloud</groupId>
  36. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-starter-web</artifactId>
  41. </dependency>
  42. </dependencies>
  43. <build>
  44. <plugins>
  45. <plugin>
  46. <groupId>org.apache.maven.plugins</groupId>
  47. <artifactId>maven-compiler-plugin</artifactId>
  48. <version>3.1</version>
  49. <configuration>
  50. <source>1.8</source>
  51. <target>1.8</target>
  52. <encoding>UTF-8</encoding>
  53. </configuration>
  54. </plugin>
  55. <plugin>
  56. <groupId>org.springframework.boot</groupId>
  57. <artifactId>spring-boot-maven-plugin</artifactId>
  58. <version>${spring-boot.version}</version>
  59. <configuration>
  60. <mainClass>com.alibaba.seata.sample.business.service.BusinessApplication</mainClass>
  61. <layout>ZIP</layout>
  62. </configuration>
  63. <executions>
  64. <execution>
  65. <goals>
  66. <goal>repackage</goal>
  67. </goals>
  68. </execution>
  69. </executions>
  70. </plugin>
  71. </plugins>
  72. </build>
  73. </project>

入口main类 BusinessApplication

  1. package com.alibaba.seata.sample.business.service;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.openfeign.EnableFeignClients;
  5. /**
  6. * @author slievrly
  7. */
  8. @EnableFeignClients
  9. @SpringBootApplication
  10. public class BusinessApplication {
  11. public static void main(String[] args) {
  12. SpringApplication.run(BusinessApplication.class, args);
  13. }
  14. }

service

  1. package com.alibaba.seata.sample.business.service.service;
  2. /**
  3. * @author slievrly
  4. */
  5. public interface IBusinessService {
  6. void reduceStockAndCreateOrder(int count, boolean mockException);
  7. String checkData();
  8. }

继承接口

  1. package com.alibaba.seata.sample.business.service.service.impl;
  2. import java.util.List;
  3. import java.util.concurrent.ThreadLocalRandom;
  4. import java.util.concurrent.TimeUnit;
  5. import javax.annotation.PostConstruct;
  6. import com.alibaba.seata.sample.business.service.feign.IOrderService;
  7. import com.alibaba.seata.sample.business.service.feign.IStorageService;
  8. import com.alibaba.seata.sample.business.service.service.IBusinessService;
  9. import com.alibaba.seata.sample.common.entity.Order;
  10. import com.alibaba.seata.sample.common.entity.Storage;
  11. import io.seata.core.context.RootContext;
  12. import io.seata.spring.annotation.GlobalTransactional;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.stereotype.Service;
  15. import static com.alibaba.seata.sample.common.Constants.SUCCESS_RESPONSE;
  16. /**
  17. * @author slievrly
  18. */
  19. @Service
  20. public class BusinessServiceImpl implements IBusinessService {
  21. private static final int INIT_COUNT = 100;
  22. @Autowired
  23. private IOrderService orderService;
  24. @Autowired
  25. private IStorageService storageService;
  26. private String opCommodityCode;
  27. @PostConstruct
  28. public void init() throws InterruptedException {
  29. for (int i = 0; i < 100; i++) {
  30. try {
  31. ThreadLocalRandom random = ThreadLocalRandom.current();
  32. String commodityCode = "C" + random.nextInt(999999);
  33. storageService.initStock(commodityCode, INIT_COUNT);
  34. opCommodityCode = commodityCode;
  35. break;
  36. } catch (Exception ignore) {
  37. ignore.printStackTrace();
  38. TimeUnit.MILLISECONDS.sleep(2);
  39. }
  40. }
  41. }
  42. @Override
  43. @GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-alibaba-seata")
  44. public void reduceStockAndCreateOrder(int count, boolean mockException) {
  45. String xid = RootContext.getXID();
  46. System.out.println("business-service 当前正在执行的事务xid:" + xid);
  47. String result = storageService.reduceStock(opCommodityCode, count);
  48. if (!SUCCESS_RESPONSE.equals(result)) {
  49. throw new RuntimeException("库存扣减失败! xid:" + xid);
  50. }
  51. Order order = new Order().setCommodityCode(opCommodityCode).setCount(count).setMoney(count * 100).setUserId(
  52. "system");
  53. result = orderService.createOrder(order);
  54. if (!SUCCESS_RESPONSE.equals(result)) {
  55. throw new RuntimeException("创建订单失败! xid:" + xid);
  56. }
  57. if (mockException) {
  58. throw new RuntimeException("模拟用户业务异常! xid:" + xid);
  59. }
  60. }
  61. @Override
  62. public String checkData() {
  63. StringBuilder sb = new StringBuilder();
  64. List<Order> orders = orderService.selectOrderByCode(opCommodityCode);
  65. List<Storage> storages = storageService.selectStockByCode(opCommodityCode);
  66. int orderTotal = 0;
  67. int storageTotal = 0;
  68. sb.append("<strong>当前您操作的CommodityCode是: <font color=\"#FF0000\">" + opCommodityCode + "</font></strong>");
  69. sb.append("<hr>");
  70. sb.append("<strong>数据表:</strong>");
  71. sb.append("<br/>");
  72. sb.append("<br/>");
  73. sb.append("<strong>order_tbl:</strong>");
  74. sb.append("<table border=\"2\">");
  75. sb.append("<tr>");
  76. sb.append("<td>Id</td><td>user_id</td><td>commodity_code</td><td>count</td><td>money</td>");
  77. sb.append("</tr>");
  78. for (Order order : orders) {
  79. sb.append("<tr>");
  80. sb.append("<td>" + order.getId() + "</td><td>" + order.getUserId() + "</td><td>" + order.getCommodityCode()
  81. + "</td><td>" + order.getCount() + "</td><td>" + order.getMoney() + "</td>");
  82. sb.append("</tr>");
  83. orderTotal += order.getCount();
  84. }
  85. sb.append("</table>");
  86. sb.append("<br/>");
  87. sb.append("<strong>storage_tbl:</strong>");
  88. sb.append("<table border=\"2\">");
  89. sb.append("<tr>");
  90. sb.append("<td>Id</td><td>commodity_code</td><td>count</td>");
  91. sb.append("</tr>");
  92. for (Storage storage : storages) {
  93. sb.append("<tr>");
  94. sb.append(
  95. "<td>" + storage.getId() + "</td><td>" + storage.getCommodityCode() + "</td><td>" + storage.getCount()
  96. + "</td>");
  97. sb.append("</tr>");
  98. storageTotal += storage.getCount();
  99. }
  100. sb.append("</table>");
  101. sb.append("<hr>");
  102. sb.append("<strong>数据一致性校验:</strong>");
  103. sb.append("<br/>");
  104. sb.append("<br/>");
  105. sb.append("<strong>调用前库存:</strong>");
  106. sb.append("<br/>");
  107. sb.append("初始化库存(storage_tbl): <strong><font color=\"#FF0000\">" + INIT_COUNT + "</font></strong>");
  108. sb.append("<br/>");
  109. sb.append("<br/>");
  110. sb.append("<strong>调用后库存:</strong>");
  111. sb.append("<br/>");
  112. sb.append("已卖出库存数量(order_tbl): <strong><font color=\"#FF0000\">" + orderTotal + "</font></strong>");
  113. sb.append("<br/>");
  114. sb.append("剩余库存(storage_tbl): <strong><font color=\"#FF0000\">" + storageTotal + "</font></strong>");
  115. sb.append("<br/>");
  116. sb.append("<br/>");
  117. sb.append(
  118. "<strong>结论:</strong> 库存量调用前后 <strong><font color=\"#FF0000\">" + (INIT_COUNT == orderTotal + storageTotal
  119. ? "一致" : "不一致") + "</font></strong>");
  120. sb.append("<hr>");
  121. return sb.toString();
  122. }
  123. }

feign

  1. package com.alibaba.seata.sample.business.service.feign;
  2. import java.util.List;
  3. import com.alibaba.seata.sample.common.entity.Order;
  4. import org.springframework.cloud.openfeign.FeignClient;
  5. import org.springframework.web.bind.annotation.RequestBody;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RequestParam;
  8. /**
  9. * @author slievrly
  10. */
  11. @FeignClient(name = "order-service",url = "http://127.0.0.1:8082")
  12. public interface IOrderService {
  13. @RequestMapping(value = "/createOrder")
  14. String createOrder(@RequestBody Order orders);
  15. @RequestMapping(value = "/selectOrderByCode")
  16. List<Order> selectOrderByCode(@RequestParam(name = "commodityCode") String commodityCode);
  17. }

存储

  1. package com.alibaba.seata.sample.business.service.feign;
  2. import java.util.List;
  3. import com.alibaba.seata.sample.common.entity.Storage;
  4. import org.springframework.cloud.openfeign.FeignClient;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RequestParam;
  7. /**
  8. * @author slievrly
  9. */
  10. @FeignClient(name = "storage-service",url = "http://127.0.0.1:8083")
  11. public interface IStorageService {
  12. @RequestMapping("/reduceStock")
  13. String reduceStock(@RequestParam(name = "commodityCode") String commodityCode,
  14. @RequestParam(name = "count") Integer count);
  15. @RequestMapping("/initStock")
  16. int initStock(@RequestParam(name = "commodityCode") String commodityCode,
  17. @RequestParam(name = "count") Integer count);
  18. @RequestMapping(value = "/selectStockByCode")
  19. List<Storage> selectStockByCode(@RequestParam(name = "commodityCode") String commodityCode);
  20. }

controller

  1. package com.alibaba.seata.sample.business.service.controller;
  2. import java.io.IOException;
  3. import javax.servlet.http.HttpServletResponse;
  4. import com.alibaba.seata.sample.business.service.service.IBusinessService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.PathVariable;
  8. import org.springframework.web.bind.annotation.RestController;
  9. /**
  10. * @author slievrly
  11. */
  12. @RestController
  13. public class HomeController {
  14. @Autowired
  15. private IBusinessService businessService;
  16. @GetMapping(value = "/seata/feign", produces = "application/json")
  17. public String feign(int count, boolean mockException) {
  18. StringBuilder result = new StringBuilder();
  19. if (count <= 0) {
  20. return "expect: count>0 ";
  21. }
  22. try {
  23. businessService.reduceStockAndCreateOrder(count, mockException);
  24. } catch (Exception exx) {
  25. result.append("失败:").append(exx.getMessage()).append("事务发生回滚!");
  26. }
  27. if (result.length() == 0) {
  28. result.append("调用扣减库存生成订单服务成功!");
  29. }
  30. return result.toString();
  31. }
  32. @GetMapping(value = "/seata/check", produces = "application/json")
  33. public void check(HttpServletResponse response) throws IOException {
  34. response.setContentType("text/html; charset=gb2312");
  35. response.getWriter().write(businessService.checkData());
  36. }
  37. }

application.yml

  1. spring:
  2. datasource:
  3. type: com.alibaba.druid.pool.DruidDataSource
  4. url: jdbc:mysql://rm-2zetd9474ydd1g5955o.mysql.rds.aliyuncs.com:3306/seata_training?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  5. driver-class-name: com.mysql.cj.jdbc.Driver
  6. username: seata
  7. password: seata123
  8. max-wait: 60000
  9. max-active: 100
  10. min-idle: 10
  11. initial-size: 10
  12. seata:
  13. enabled: true
  14. application-id: business
  15. tx-service-group: my_test_tx_group
  16. service:
  17. vgroup-mapping:
  18. my_test_tx_group: default
  19. grouplist:
  20. default: 127.0.0.1:8091
  21. disable-global-transaction: false

bootstrap.yml

  1. spring:
  2. application:
  3. name: business-service
  4. main:
  5. allow-bean-definition-overriding: true
  6. cloud:
  7. loadbalancer:
  8. retry:
  9. enabled: false
  10. server:
  11. port: 60000
  12. ribbon:
  13. ConnectTimeout: 100000
  14. ReadTimeout: 1000000
  15. OkToRetryOnAllOperations: false
  16. feign:
  17. httpclient:
  18. enabled: true
  19. connection-timeout: 60000
  20. client:
  21. config:
  22. default:
  23. connectTimeout: 60000
  24. readTimeout: 60000
  25. loggerLevel: FULL

order-service

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>seata-sample</artifactId>
  7. <groupId>com.alibaba.seata</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>order-service</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>${project.groupId}</groupId>
  15. <artifactId>common-service</artifactId>
  16. <version>${project.version}</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-openfeign</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.cloud</groupId>
  24. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>io.seata</groupId>
  28. <artifactId>seata-spring-boot-starter</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>mysql</groupId>
  32. <artifactId>mysql-connector-java</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.mybatis.spring.boot</groupId>
  36. <artifactId>mybatis-spring-boot-starter</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>com.alibaba.cloud</groupId>
  40. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  41. </dependency>
  42. <dependency>
  43. <groupId>org.springframework.boot</groupId>
  44. <artifactId>spring-boot-starter-web</artifactId>
  45. </dependency>
  46. </dependencies>
  47. <build>
  48. <plugins>
  49. <plugin>
  50. <groupId>org.apache.maven.plugins</groupId>
  51. <artifactId>maven-compiler-plugin</artifactId>
  52. <version>3.1</version>
  53. <configuration>
  54. <source>1.8</source>
  55. <target>1.8</target>
  56. <encoding>UTF-8</encoding>
  57. </configuration>
  58. </plugin>
  59. <plugin>
  60. <groupId>org.springframework.boot</groupId>
  61. <artifactId>spring-boot-maven-plugin</artifactId>
  62. <version>${spring-boot.version}</version>
  63. <configuration>
  64. <mainClass>com.alibaba.seata.sample.order.service.OrderApplication</mainClass>
  65. <layout>ZIP</layout>
  66. </configuration>
  67. <executions>
  68. <execution>
  69. <goals>
  70. <goal>repackage</goal>
  71. </goals>
  72. </execution>
  73. </executions>
  74. </plugin>
  75. </plugins>
  76. </build>
  77. </project>

入口类

  1. package com.alibaba.seata.sample.order.service;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.openfeign.EnableFeignClients;
  6. /**
  7. * @author slievrly
  8. */
  9. @SpringBootApplication
  10. @EnableFeignClients
  11. @EnableDiscoveryClient
  12. public class OrderApplication {
  13. public static void main(String[] args) {
  14. SpringApplication.run(OrderApplication.class, args);
  15. }
  16. }

service

  1. package com.alibaba.seata.sample.order.service.service;
  2. import java.util.List;
  3. import com.alibaba.seata.sample.common.entity.Order;
  4. /**
  5. * @author slievrly
  6. */
  7. public interface IOrderService {
  8. void createOrder(Order order);
  9. List<Order> selectOrderByCode(String commodityCode);
  10. }

服务实现类

  1. package com.alibaba.seata.sample.order.service.service.impl;
  2. import java.util.List;
  3. import com.alibaba.seata.sample.common.entity.Order;
  4. import com.alibaba.seata.sample.order.service.mapper.OrderMapper;
  5. import com.alibaba.seata.sample.order.service.service.IOrderService;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. /**
  9. * @author slievrly
  10. */
  11. @Service
  12. public class OrderServiceImpl implements IOrderService {
  13. @Autowired
  14. private OrderMapper orderMapper;
  15. @Override
  16. public void createOrder(Order order) {
  17. orderMapper.insert(order);
  18. }
  19. @Override
  20. public List<Order> selectOrderByCode(String commodityCode) {
  21. return orderMapper.selectOrderByCode(commodityCode);
  22. }
  23. }

数据maper

  1. import org.apache.ibatis.annotations.Mapper;
  2. /**
  3. * @author slievrly
  4. */
  5. @Mapper
  6. public interface OrderMapper {
  7. int insert(Order order);
  8. List<Order> selectOrderByCode(String commodityCode);
  9. }

resources/mapper/OrderMapper.xml

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
  3. <mapper namespace="com.alibaba.seata.sample.order.service.mapper.OrderMapper">
  4. <resultMap id="BaseResultMap" type="com.alibaba.seata.sample.common.entity.Order">
  5. <id column="id" property="id" jdbcType="INTEGER"/>
  6. <result column="user_id" property="userId" jdbcType="VARCHAR"/>
  7. <result column="commodity_code" property="commodityCode" jdbcType="VARCHAR"/>
  8. <result column="count" property="count" jdbcType="INTEGER"/>
  9. <result column="money" property="money" jdbcType="INTEGER"/>
  10. </resultMap>
  11. <insert id="insert" parameterType="com.alibaba.seata.sample.common.entity.Order">
  12. insert into order_tbl (user_id,commodity_code,count,money)
  13. values (#{userId,jdbcType=VARCHAR}, #{commodityCode,jdbcType=VARCHAR}, #{count,jdbcType=INTEGER}, #{money,jdbcType=INTEGER})
  14. </insert>
  15. <select id="selectOrderByCode" parameterType="java.lang.String" resultMap="BaseResultMap">
  16. select * from order_tbl where commodity_code=#{commodityCode};
  17. </select>
  18. </mapper>

控制类

  1. package com.alibaba.seata.sample.order.service.controller;
  2. import java.util.List;
  3. import com.alibaba.seata.sample.common.Constants;
  4. import com.alibaba.seata.sample.common.entity.Order;
  5. import com.alibaba.seata.sample.order.service.service.IOrderService;
  6. import io.seata.core.context.RootContext;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.web.bind.annotation.RequestBody;
  9. import org.springframework.web.bind.annotation.RequestMapping;
  10. import org.springframework.web.bind.annotation.RequestMethod;
  11. import org.springframework.web.bind.annotation.RestController;
  12. /**
  13. * @author slievrly
  14. */
  15. @RestController
  16. public class OrderController {
  17. @Autowired
  18. private IOrderService orderService;
  19. @RequestMapping(value = "/createOrder", method = RequestMethod.POST, produces = "application/json")
  20. public String create(@RequestBody Order order) {
  21. System.out.println("order-service 当前正在执行的事务xid:" + RootContext.getXID());
  22. try {
  23. orderService.createOrder(order);
  24. } catch (Exception exx) {
  25. exx.printStackTrace();
  26. return Constants.FAIL_RESPONSE;
  27. }
  28. return Constants.SUCCESS_RESPONSE;
  29. }
  30. @RequestMapping(value = "/selectOrderByCode", method = RequestMethod.GET, produces = "application/json")
  31. public List<Order> selectOrderByCode(String commodityCode) {
  32. return orderService.selectOrderByCode(commodityCode);
  33. }
  34. }

application.yml

  1. spring:
  2. datasource:
  3. type: com.alibaba.druid.pool.DruidDataSource
  4. url: jdbc:mysql://rm-2zetd9474ydd1g5955o.mysql.rds.aliyuncs.com:3306/seata_training?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  5. driver-class-name: com.mysql.cj.jdbc.Driver
  6. username: seata
  7. password: seata123
  8. max-wait: 60000
  9. max-active: 100
  10. min-idle: 10
  11. initial-size: 10
  12. mybatis:
  13. mapper-locations: classpath*:mapper/*Mapper.xml
  14. seata:
  15. enabled: true
  16. application-id: order
  17. tx-service-group: my_test_tx_group
  18. service:
  19. vgroup-mapping:
  20. my_test_tx_group: default
  21. grouplist:
  22. default: 127.0.0.1:8091
  23. disable-global-transaction: false

bootstrap.yml

  1. spring:
  2. application:
  3. name: order-service
  4. main:
  5. allow-bean-definition-overriding: true
  6. cloud:
  7. loadbalancer:
  8. retry:
  9. enabled: false
  10. server:
  11. port: 8082
  12. ribbon:
  13. ConnectTimeout: 100000
  14. ReadTimeout: 1000000
  15. OkToRetryOnAllOperations: false

storage-service

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>seata-sample</artifactId>
  7. <groupId>com.alibaba.seata</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>storage-service</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>${project.groupId}</groupId>
  15. <artifactId>common-service</artifactId>
  16. <version>${project.version}</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-openfeign</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.cloud</groupId>
  24. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>io.seata</groupId>
  28. <artifactId>seata-spring-boot-starter</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>mysql</groupId>
  32. <artifactId>mysql-connector-java</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.mybatis.spring.boot</groupId>
  36. <artifactId>mybatis-spring-boot-starter</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>com.alibaba.cloud</groupId>
  40. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  41. </dependency>
  42. <dependency>
  43. <groupId>org.springframework.boot</groupId>
  44. <artifactId>spring-boot-starter-web</artifactId>
  45. </dependency>
  46. </dependencies>
  47. <build>
  48. <plugins>
  49. <plugin>
  50. <groupId>org.apache.maven.plugins</groupId>
  51. <artifactId>maven-compiler-plugin</artifactId>
  52. <version>3.1</version>
  53. <configuration>
  54. <source>1.8</source>
  55. <target>1.8</target>
  56. <encoding>UTF-8</encoding>
  57. </configuration>
  58. </plugin>
  59. <plugin>
  60. <groupId>org.springframework.boot</groupId>
  61. <artifactId>spring-boot-maven-plugin</artifactId>
  62. <version>${spring-boot.version}</version>
  63. <configuration>
  64. <mainClass>com.alibaba.seata.sample.storage.service.StorageApplication</mainClass>
  65. <layout>ZIP</layout>
  66. </configuration>
  67. <executions>
  68. <execution>
  69. <goals>
  70. <goal>repackage</goal>
  71. </goals>
  72. </execution>
  73. </executions>
  74. </plugin>
  75. </plugins>
  76. </build>
  77. </project>

入口类

  1. package com.alibaba.seata.sample.storage.service;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. /**
  5. * @author slievrly
  6. */
  7. @SpringBootApplication
  8. public class StorageApplication {
  9. public static void main(String[] args) {
  10. SpringApplication.run(StorageApplication.class, args);
  11. }
  12. }

service 接口

  1. package com.alibaba.seata.sample.storage.service.service;
  2. import java.util.List;
  3. import com.alibaba.seata.sample.common.entity.Storage;
  4. /**
  5. * @author slievrly
  6. */
  7. public interface IStorageService {
  8. int reduceStock(String commodityCode, int count);
  9. int initStock(String commodityCode, int count);
  10. List<Storage> selectStockByCode(String commodityCode);
  11. }

接口实现

  1. package com.alibaba.seata.sample.storage.service.service.impl;
  2. import java.util.List;
  3. import com.alibaba.seata.sample.common.entity.Storage;
  4. import com.alibaba.seata.sample.storage.service.mapper.StorageMapper;
  5. import com.alibaba.seata.sample.storage.service.service.IStorageService;
  6. import io.seata.core.context.RootContext;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.stereotype.Service;
  9. /**
  10. * @author slievrly
  11. */
  12. @Service
  13. public class StorageServiceImpl implements IStorageService {
  14. @Autowired
  15. private StorageMapper storageMapper;
  16. @Override
  17. public int reduceStock(String commodityCode, int count) {
  18. System.out.println("storage-service 当前正在执行的事务xid:" + RootContext.getXID());
  19. return storageMapper.updateStockByCode(commodityCode, count);
  20. }
  21. @Override
  22. public int initStock(String commodityCode, int count) {
  23. return storageMapper.insert(new Storage().setCommodityCode(commodityCode).setCount(count));
  24. }
  25. @Override
  26. public List<Storage> selectStockByCode(String commodityCode) {
  27. return storageMapper.selectStockByCode(commodityCode);
  28. }
  29. }

数据mapper

  1. package com.alibaba.seata.sample.storage.service.mapper;
  2. import java.util.List;
  3. import com.alibaba.seata.sample.common.entity.Storage;
  4. import org.apache.ibatis.annotations.Mapper;
  5. import org.apache.ibatis.annotations.Param;
  6. /**
  7. * @author slievrly
  8. */
  9. @Mapper
  10. public interface StorageMapper {
  11. Storage selectById(@Param("id") Integer id);
  12. List<Storage> selectStockByCode(@Param("commodityCode") String commodityCode);
  13. int updateStockByCode(@Param("commodityCode") String commodityCode, @Param("count") int count);
  14. int insert(Storage storage);
  15. }

resource/mapper/StorageMapper.xml

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
  3. <mapper namespace="com.alibaba.seata.sample.storage.service.mapper.StorageMapper">
  4. <resultMap id="BaseResultMap" type="com.alibaba.seata.sample.common.entity.Storage">
  5. <id column="id" property="id" jdbcType="INTEGER"/>
  6. <result column="commodity_code" property="commodityCode" jdbcType="VARCHAR"/>
  7. <result column="count" property="count" jdbcType="INTEGER"/>
  8. </resultMap>
  9. <select id="selectById" resultMap="BaseResultMap">
  10. select id, commodity_code, count from storage_tbl
  11. WHERE id = #{id}
  12. </select>
  13. <select id="selectAll" resultMap="BaseResultMap">
  14. select id, commodity_code, count from storage_tbl
  15. </select>
  16. <select id="selectStockByCode" parameterType="java.lang.String" resultMap="BaseResultMap">
  17. select id, commodity_code, count from storage_tbl
  18. WHERE commodity_code = #{commodityCode}
  19. </select>
  20. <update id="updateStockByCode">
  21. update storage_tbl set count = count- #{count}
  22. WHERE commodity_code = #{commodityCode} and count &gt;= #{count}
  23. </update>
  24. <insert id="insert" parameterType="com.alibaba.seata.sample.common.entity.Storage">
  25. insert into storage_tbl (commodity_code, count)
  26. values (#{commodityCode,jdbcType=VARCHAR}, #{count,jdbcType=INTEGER})
  27. </insert>
  28. <delete id="delete" parameterType="java.lang.String">
  29. delete from storage_tbl where commodity_code=#{commodityCode}
  30. </delete>
  31. </mapper>

控制类

  1. package com.alibaba.seata.sample.storage.service.controller;
  2. import java.util.List;
  3. import com.alibaba.seata.sample.common.Constants;
  4. import com.alibaba.seata.sample.common.entity.Storage;
  5. import com.alibaba.seata.sample.storage.service.service.IStorageService;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RequestParam;
  9. import org.springframework.web.bind.annotation.RestController;
  10. /**
  11. * @author slievrly
  12. */
  13. @RestController
  14. public class StorageController {
  15. @Autowired
  16. IStorageService storageService;
  17. @RequestMapping("/reduceStock")
  18. public String reduceStock(@RequestParam(name = "commodityCode") String commodityCode,
  19. @RequestParam(name = "count") Integer count) {
  20. return storageService.reduceStock(commodityCode, count) == 1 ? Constants.SUCCESS_RESPONSE
  21. : Constants.FAIL_RESPONSE;
  22. }
  23. @RequestMapping("/initStock")
  24. public int initStock(@RequestParam(name = "commodityCode") String commodityCode,
  25. @RequestParam(name = "count") Integer count) {
  26. return storageService.initStock(commodityCode, count);
  27. }
  28. @RequestMapping("/selectStockByCode")
  29. public List<Storage> selectStockByCode(@RequestParam(name = "commodityCode") String commodityCode) {
  30. return storageService.selectStockByCode(commodityCode);
  31. }
  32. }

application.yml

  1. spring:
  2. datasource:
  3. type: com.alibaba.druid.pool.DruidDataSource
  4. url: jdbc:mysql://rm-2zetd9474ydd1g5955o.mysql.rds.aliyuncs.com:3306/seata_training?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  5. driver-class-name: com.mysql.cj.jdbc.Driver
  6. username: seata
  7. password: seata123
  8. max-wait: 60000
  9. max-active: 100
  10. min-idle: 10
  11. initial-size: 10
  12. mybatis:
  13. mapper-locations: classpath*:mapper/*Mapper.xml
  14. seata:
  15. enabled: true
  16. application-id: storage
  17. tx-service-group: my_test_tx_group
  18. service:
  19. vgroup-mapping:
  20. my_test_tx_group: default
  21. grouplist:
  22. default: 127.0.0.1:8091
  23. disable-global-transaction: false

bootstrap.yml

  1. spring:
  2. application:
  3. name: storage-service
  4. main:
  5. allow-bean-definition-overriding: true
  6. cloud:
  7. loadbalancer:
  8. retry:
  9. enabled: false
  10. server:
  11. port: 8083
  12. ribbon:
  13. ConnectTimeout: 100000
  14. ReadTimeout: 1000000
  15. OkToRetryOnAllOperations: false

本实验提供了3个工程,其调用关系如下:
image.png
如图所示,服务链路中包含三个微服务:business(微服务入口)、order(订单服务)和 storage(库存服务)。
业务逻辑

  1. 通过url 调用 business 服务,business 服务会通过 openFeign 的方式分别调用 storage 和 order 服务。
  2. 每个服务调用后会根据正常(ok)或异常(fail)的返回值决定是否抛出异常,若返回结果不为 ok 那么 business 将抛出异常,触发整个事务回滚,预期数据至 business 方法执行前的数据并且库存总量和与初始值一致。
  3. 当order 和 storage 两个服务调用正常,用户可以根据 url 中的 mockException=true 或 false 来注入一个 mock 异常,当注入异常后,期望数据回滚至 business 方法执行前的数据并且库存总量和与初始值一致。

异常逻辑

  1. 在 bussiness 中请求超过现有库存,通过参数 count 来指定要扣减的库存数量,当超出库存,将抛出异常进行事务回滚。
  2. 在 bussiness 中请求中加入 mockException=true 参数,触发事务回滚。

undo.sql

  1. CREATE TABLE undo_log
  2. (
  3. id BIGINT(20) NOT NULL AUTO_INCREMENT,
  4. branch_id BIGINT(20) NOT NULL,
  5. xid VARCHAR(100) NOT NULL,
  6. context VARCHAR(128) NOT NULL,
  7. rollback_info LONGBLOB NOT NULL,
  8. log_status INT(11) NOT NULL,
  9. log_created DATETIME NOT NULL,
  10. log_modified DATETIME NOT NULL,
  11. PRIMARY KEY (id),
  12. UNIQUE KEY ux_undo_log (xid, branch_id)
  13. ) ENGINE = InnoDB;

参考

https://doc.ruoyi.vip/ruoyi-cloud/cloud/seata.html#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8