在前面一篇咱們介紹了Seata 分佈式事務系列一: Seata 服務端搭建.html
這一篇咱們使用Seata 進行一個 Dubbo 微服務分佈式事務的示例. 本示例包含 4 個項目:java
四者之間的調用關係以下圖所示:mysql
API 模塊爲一個普通的 SpringBoot Web Restful API服務, 爲 Dubbo 服務消費者. 在 purchase() 中, 啓動全局事務.web
<?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>fun.faceless.dubboshop.api</groupId> <artifactId>parent</artifactId> <packaging>pom</packaging> <version>1.0.0-SNAPSHOT</version> <name>api-center</name> <description>dubboshop api center</description> <properties> <java.version>1.8</java.version> <spring-boot.version>2.1.5.RELEASE</spring-boot.version> <spring.version>5.1.7.RELEASE</spring.version> <dubbo.version>2.7.7</dubbo.version> </properties> <dependencyManagement> <dependencies> <!-- springboot related --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <version>${spring-boot.version}</version> <scope>runtime</scope> </dependency> <!-- db data --> <!-- 對應 mybatis 3.5.5,從3.5.0開始支持mapper返回Optional --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis.dynamic-sql/mybatis-dynamic-sql --> <dependency> <groupId>org.mybatis.dynamic-sql</groupId> <artifactId>mybatis-dynamic-sql</artifactId> <version>1.1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.29</version> </dependency> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.2</version> </dependency> <!--jackson依賴--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.5</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.5</version> </dependency> <!-- rocketmq --> <!--<dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>4.7.0</version> </dependency>--> <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <!-- caching --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.ehcache</groupId> <version>3.6.3</version> <artifactId>ehcache</artifactId> </dependency> <dependency> <groupId>javax.cache</groupId> <artifactId>cache-api</artifactId> <version>1.1.0</version> </dependency> <!-- Seata --> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <!-- Dubbo dependencies --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-bom</artifactId> <version>${dubbo.version}</version> <type>pom</type> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> <exclusions> <exclusion> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>${dubbo.version}</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-metadata-report-zookeeper</artifactId> <version>${dubbo.version}</version> </dependency> <!-- dubbo REST protocal --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-rpc-rest</artifactId> <version>${dubbo.version}</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>8.5.32</version> </dependency> <!-- Zookeeper dependencies--> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-dependencies-zookeeper</artifactId> <version>2.7.1</version> <type>pom</type> <exclusions> <exclusion> <artifactId>log4j</artifactId> <groupId>log4j</groupId> </exclusion> <exclusion> <artifactId>slf4j-log4j12</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> <!-- inter-project modules --> <dependency> <groupId>fun.faceless.dubboshop</groupId> <artifactId>comms</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>fun.faceless.dubboshop.inventory</groupId> <artifactId>dubboapi</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>fun.faceless.dubboshop.account</groupId> <artifactId>dubboapi</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>fun.faceless.dubboshop.order</groupId> <artifactId>dubboapi</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <!-- other dependencies --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> <optional>true</optional> </dependency> <!-- testing related --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring-boot.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- 將上面經過 dependencyManagement 指定版本號的依賴根據須要放到這裏... --> </dependencies>
在 application.yaml
中配置 seata:redis
spring: # 這裏省略spring相關的常規配置... dubbo: # 註冊中心配置 registry: id: dubboshop-registry address: zookeeper://<your_zk_host>:2181 group: dubbo simplified: true metadata-report: address: zookeeper://<your_zk_host>:2181 group: dubbo application: name: dubboshop-api id: dubboshop-api logger: slf4j # 這裏 seata 未使用註冊中心. 若是使用過 seata: application-id: dubboshop-api # Seata 應用編號, tx-service-group: dubboshop-api-group # Seata 事務組編號,用於 TC 集羣名 # Seata 服務配置項,對應 ServiceProperties 類 service: # 虛擬組和分組的映射 vgroup-mapping: dubboshop-api-group: default # 這裏 dubboshop-api-group 對應上面 tx-service-group 的值. # 分組和 Seata 服務的映射. 不適用 Nacos 註冊中心時, 配置grouplist, 不然不須要該項. grouplist: default: <your_seata_server_host>:8091
項目啓動WebApplication
:spring
@SpringBootApplication @RestController @ComponentScan("fun.faceless.dubboshop.api") public class WebApplication { public static void main(String[] args) { SpringApplication.run(WebApplication.class, args); } @GetMapping("/") public String home() { return String.format("<html>Welcome to %s</html>", "DubboShopApiCenter"); } }
業務代碼, 這裏爲了方便, 事務直接放在了OrderController
中, 沒有單獨去寫一個Service了.sql
@RestController @RequestMapping("/api") @Slf4j public class OrderController { // 在 dubbo 2.7.x 中, cluster="failfast" 存在bug, 沒法正常生效, 因此須要設置 retries=0. @Reference(protocol = "dubbo", cluster="failfast", retries=0) OrderProvider orderProvider; @Reference(protocol = "dubbo", cluster="failfast", retries=0) AccountCreditProvider accountCreditProvider; private String dubboConfigCenterAddr = "yyadmin:2181"; @PostMapping(value = "purchase") @GlobalTransactional // 注意這裏啓動全局事務 public Result purchase(@RequestParam int userId, @RequestParam int goodsId, @RequestParam int goodsNum) { log.info("當前 XID: {}", RootContext.getXID()); int unitCredit = 10; int creditAmount = goodsNum * unitCredit; accountCreditProvider.debit(userId, creditAmount); orderProvider.createOrder(userId, goodsId, goodsNum); return Result.succ("Order created successfully."); } }
其餘微服務指 Account, Inventory, Order 3 個微服務項目. 三者的配置和方式都基本同樣, 核心就是在相關 Service 上使用 @Transactional
進行本地事務管理. 此外, 能夠經過 RootContext
獲取全局事務ID: xid.數據庫
微服務項目的 dubbo 和 seata 有關依賴跟 api項目的依賴基本一致. 能夠直接參考上面.apache
各微服務項目 dubbo 和 seata 有關的配置也基本一致. 只不過這三者做爲微服務提供者, dubbo 相關的會多一些 protocol 配置. 這裏以 Order 項目爲例:segmentfault
# ========= Dubbo Provider ============== dubbo: # 配置中心, see: http://dubbo.apache.org/zh-cn/docs/user/configuration/config-center.html config-center: address: zookeeper://<your_zk_host>:2181 group: dubbo # 註冊中心配置 registry: id: dubboshop-registry address: zookeeper://<your_zk_host>:2181 group: dubbo simplified: true check: true metadata-report: address: zookeeper://<your_zk_host>:2181 group: dubbo application: name: dubboshop-order id: dubboshop-order logger: slf4j # 示例多 Protocol, 這裏支持 dubbo 和 rest 兩種 Protocols. protocols: dubbo: name: dubbo server: netty4 port: 20884 accesslog: true rest: name: rest server: tomcat port: 8084 accesslog: true scan: # dubbo 服務提供者實現類所在包 base-packages: fun.faceless.dubboshop.order.dubboprovider # Seata 配置項,對應 SeataProperties 類 seata: application-id: dubboshop-order # Seata 應用編號,默認爲 ${spring.application.name} tx-service-group: dubboshop-order-group # Seata 事務組編號,用於 TC 集羣名 # Seata 服務配置項,對應 ServiceProperties 類 service: # 虛擬組和分組的映射 vgroup-mapping: dubboshop-order-group: default # 這裏 dubboshop-order-group 對應 上面 tx-service-group 的值. # 分組和 Seata 服務的映射. 不適用 Nacos 註冊中心時, 配置grouplist, 不然不須要該項. grouplist: default: <your_seata_server_host>:8091 # 本來純dubbo服務不須要配置 server.port, 不過 Seata 會自動啓動 SpringBoot Web, 須要配置一個端口. 注意若是是同一主機上啓動這幾個服務, 這個端口取不一樣的值. 不然會報端口占用異常. server: port: 28084
@Slf4j @Service(cluster="failfast", retries=0, timeout = 2000) public class OrderProviderImpl implements OrderProvider { // 本地持久化 DAO @Autowired private OrderDao orderDao; // Dubbo調用 InventoryProvider @Reference(protocol="dubbo", cluster="failfast", retries=0, timeout = 1500) private InventoryProvider inventoryProvider; /** * 調用Dubbo服務, 扣除庫存. 而後提交數據庫, 建立一個新訂單. * * @param userId * @param goodsId * @param orderCount * @return 返回受影響的記錄行數 */ @Override @Transactional // <- 注意這裏須要本地事務 public int createOrder(int userId, int goodsId, int orderCount) { log.info("當前 XID: {}", RootContext.getXID()); Result debitResult = inventoryProvider.deduce(goodsId, orderCount); return this.saveOrder(userId, goodsId, orderCount); } private int saveOrder(int userId, int goodsId, int orderCount) { Order order = new Order(); order.setUserId(userId); order.setState((short) 10); order.setGoodsInfo(prepareNewGoodsInfo(goodsId, orderCount)); int recordsAffected = orderDao.createOrder(order); return recordsAffected; } // 省略其餘業務代碼 }