Seata 分布式事务系列二: Dubbo分布式事务

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

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

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

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

一. API 服务

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

1.1 maven依赖



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.5.RELEASE
         
    

    fun.faceless.dubboshop.api
    parent
    pom
    1.0.0-SNAPSHOT
    api-center
    dubboshop api center

    
        1.8
        2.1.5.RELEASE
        5.1.7.RELEASE
        2.7.7
    


    
        
            
            
                org.springframework.boot
                spring-boot-starter-web
                ${spring-boot.version}
            
            
                org.springframework.boot
                spring-boot-devtools
                ${spring-boot.version}
                runtime
            

            
            
            
                org.mybatis.spring.boot
                mybatis-spring-boot-starter
                2.1.3
            
            
            
                org.mybatis.dynamic-sql
                mybatis-dynamic-sql
                1.1.4
            
            
                mysql
                mysql-connector-java
                8.0.16
            
            
                com.alibaba
                druid
                1.0.29
            

            
            
                org.springframework.boot
                spring-boot-starter-data-redis
                ${spring-boot.version}
            
            
                org.apache.commons
                commons-pool2
                2.6.2
            
            
            
                com.fasterxml.jackson.core
                jackson-core
                2.9.5
            
            
                com.fasterxml.jackson.core
                jackson-databind
                2.9.5
            

            
            
            
                org.apache.rocketmq
                rocketmq-spring-boot-starter
                2.1.0
            

            
            
                org.springframework.boot
                spring-boot-starter-cache
                ${spring-boot.version}
            
            
                org.ehcache
                3.6.3
                ehcache
            
            
                javax.cache
                cache-api
                1.1.0
            

            
            
                io.seata
                seata-spring-boot-starter
                1.3.0
            

            
            
                org.apache.dubbo
                dubbo-bom
                ${dubbo.version}
                pom
            
            
                org.apache.dubbo
                dubbo
                ${dubbo.version}
                
                    
                        org.apache.thrift
                        libthrift
                    
                
            
            
                org.apache.dubbo
                dubbo-spring-boot-starter
                ${dubbo.version}
            
            
                org.apache.dubbo
                dubbo-metadata-report-zookeeper
                ${dubbo.version}
            

            
            
                org.apache.dubbo
                dubbo-rpc-rest
                ${dubbo.version}
            
            
                org.apache.tomcat.embed
                tomcat-embed-core
                8.5.32
            

            
            
                org.apache.dubbo
                dubbo-dependencies-zookeeper
                2.7.1
                pom
                
                    
                        log4j
                        log4j
                    
                    
                        slf4j-log4j12
                        org.slf4j
                    
                
            

            
            
                fun.faceless.dubboshop
                comms
                1.0.0-SNAPSHOT
            
            
                fun.faceless.dubboshop.inventory
                dubboapi
                1.0.0-SNAPSHOT
            
            
                fun.faceless.dubboshop.account
                dubboapi
                1.0.0-SNAPSHOT
            
            
                fun.faceless.dubboshop.order
                dubboapi
                1.0.0-SNAPSHOT
            

            
            
                org.projectlombok
                lombok
                1.18.8
                true
            

            
            
                org.springframework.boot
                spring-boot-starter-test
                ${spring-boot.version}
            
        
    

    
        
    

1.2 项目配置文件

application.yaml中配置 seata:

spring:
  # 这里省略spring相关的常规配置...
  
dubbo:
  # 注册中心配置
  registry:
    id: dubboshop-registry
    address: zookeeper://:2181
    group: dubbo
    simplified: true
  metadata-report:
    address: zookeeper://: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: :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("Welcome to %s", "DubboShopApiCenter");
    }
}

业务代码, 这里为了方便, 事务直接放在了OrderController中, 没有单独去写一个Service了.

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

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://:2181
    group: dubbo
  # 注册中心配置
  registry:
    id: dubboshop-registry
    address: zookeeper://:2181
    group: dubbo
    simplified: true
    check: true
  metadata-report:
    address: zookeeper://: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: :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;
    }
    // 省略其他业务代码
}

你可能感兴趣的:(seata,java,springboot,dubbo,分布式事务)