在后面一篇咱们介绍了Seata 分布式事务系列一: Seata 服务端搭建.

这一篇咱们应用Seata 进行一个 Dubbo 微服务分布式事务的示例. 本示例蕴含 4 个我的项目:

  • API Rest服务, 对立对外的业务模块, 这里示例一个购物服务 purchase(). 其 Dubbo 身份为服务消费者.
  • Account 账户微服务, 下单前需扣除账户余额. 其 Dubbo 身份为服务提供者.
  • Order 订单微服务, 创立订单. 其 Dubbo 身份为服务消费者 + 提供者.
  • Inventory 库存微服务, 创立订单前需扣除商品库存. 其 Dubbo 身份为服务提供者.

四者之间的调用关系如下图所示:

一. API 服务

API 模块为一个一般的 SpringBoot Web Restful API服务, 为 Dubbo 服务消费者. 在 purchase() 中, 启动全局事务.

1.1 maven依赖

<?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>

1.2 我的项目配置文件

application.yaml中配置 seata:

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

1.3 业务代码

我的项目启动WebApplication:

@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了.

@RestController@RequestMapping("/api")@Slf4jpublic 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.

2.1 Maven依赖

微服务项目的 dubbo 和 seata 无关依赖跟 api我的项目的依赖基本一致. 能够间接参考下面.

2.2 我的项目配置

各微服务项目 dubbo 和 seata 无关的配置也基本一致. 只不过这三者作为微服务提供者, dubbo 相干的会多一些 protocol 配置. 这里以 Order 我的项目为例:

# ========= 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

2.3 业务代码

@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;    }    // 省略其余业务代码}