事务管理

本示例演示了如何通过 Seata 实现分布式 Dubbo 服务的事务管理,以确保数据一致性。

什么是 Seata

Seata 是一款开源分布式事务解决方案,致力于为用户提供高性能、易于使用的分布式事务服务。Seata 将为用户提供 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式分布式解决方案。

1. 示例架构描述

用户购买商品业务,整个业务包含 3 个微服务

  • 库存服务:扣减指定商品的库存数量。
  • 订单服务:根据购买请求生成订单。
  • 账户服务:扣减用户账户金额。

image.png

StorageService

public interface StorageService {

     /**
      * Deducted storage quantity
      */
     void deduct(String commodityCode, int count);
}

OrderService

public interface OrderService {

     /**
      * Create Order
      */
     Order create(String userId, String commodityCode, int orderCount);
}

AccountService

public interface AccountService {

     /**
      * Borrow from user account
      */
     void debit(String userId, int money);
}

二、主要业务逻辑

BusinessService

public class BusinessServiceImpl implements BusinessService {

     private StorageService storageService;

     private OrderService orderService;

     /**
      * Purchasing
      */
     public void purchase(String userId, String commodityCode, int orderCount) {
         // Deduct storage amount
         storageService.deduct(commodityCode, orderCount);
         // Create Order
         orderService.create(userId, commodityCode, orderCount);
     }
}

StorageService

public class StorageServiceImpl implements StorageService {

     private JdbcTemplate jdbcTemplate;
  
     @Override
     public void deduct(String commodityCode, int count) {
         // Modify the database: deduct the amount of storage
         jdbcTemplate.update("update storage_tbl set count = count - ? where commodity_code = ?",
                 new Object[]{count, commodityCode});
     }
}

OrderService

public class OrderServiceImpl implements OrderService {

     private AccountService accountService;
    
     private JdbcTemplate jdbcTemplate;

     public Order create(String userId, String commodityCode, int orderCount) {
         // calculate the amount
         int orderMoney = calculate(commodityCode, orderCount);

         // The amount deducted from the user account
         accountService.debit(userId, orderMoney);

         // Modify the database: create a new order
         final Order order = new Order();
         order.userId = userId;
         order.commodityCode = commodityCode;
         order.count = orderCount;
         order.money = orderMoney;
         KeyHolder keyHolder = new GeneratedKeyHolder();
         jdbcTemplate. update(con -> {
             PreparedStatement pst = con. prepareStatement(
                     "insert into order_tbl (user_id, commodity_code, count, money) values (?, ?, ?, ?)",
                     PreparedStatement. RETURN_GENERATED_KEYS);
             pst.setObject(1, order.userId);
             pst.setObject(2, order.commodityCode);
             pst.setObject(3, order.count);
             pst.setObject(4, order.money);
             return pst;
         }, keyHolder);
         order.id = keyHolder.getKey().longValue();
         return order;
     }
}

AccountService

public class AccountServiceImpl implements AccountService {
        
     private JdbcTemplate jdbcTemplate;
    
     @Override
     public void debit(String userId, int money) {
         // Modify the database: deduct the amount from the user account
         jdbcTemplate.update("update account_tbl set money = money - ? where user_id = ?", new Object[]{money, userId});
     }
}

3. 快速入门示例

步骤 1:下载源代码

git clone -b master https://github.com/apache/dubbo-samples.git
cd ./dubbo-samples-transaction/

步骤 2:通过 docker-compose 启动 Seata-Server 和 MySQL

在本示例中,我们使用 docker-compose 快速拉起 seata-server 和 mysql 等服务。

cd src/main/resources/docker
docker-compose up

步骤 3:构建用例

执行 maven 命令打包 demo 项目

mvn clean package

步骤 4:启动 AccountService

java -classpath ./target/dubbo-samples-transaction-1.0-SNAPSHOT.jar org.apache.dubbo.samples.starter.DubboAccountServiceStarter

步骤 5:启动 OrderService

java -classpath ./target/dubbo-samples-transaction-1.0-SNAPSHOT.jar org.apache.dubbo.samples.starter.DubboOrderServiceStarter

步骤 6:启动 StorageService

java -classpath ./target/dubbo-samples-transaction-1.0-SNAPSHOT.jar org.apache.dubbo.samples.starter.DubboStorageServiceStarter

步骤 7:启动 BusinessService

java -classpath ./target/dubbo-samples-transaction-1.0-SNAPSHOT.jar org.apache.dubbo.samples.starter.DubboBusinessTester

4. 示例核心流程

image.png

步骤 1:修改业务代码

这里只需要在业务发起方方法上写一行注解 @GlobalTransactional

     @GlobalTransactional
     public void purchase(String userId, String commodityCode, int orderCount) {
          …
     }

步骤 2:安装数据库

  • 要求:MySQL(InnoDB 存储引擎)。

提示: 实际上,示例中的 3 个微服务需要 3 个独立的数据库,但为了方便起见,我们使用同一个物理数据库,并配置 3 个逻辑连接字符串。

修改以下 xml 文件中的数据库 url、用户名和密码

dubbo-account-service.xml dubbo-order-service.xml dubbo-storage-service.xml

     <property name="url" value="jdbc:mysql://x.x.x.x:3306/xxx" />
     <property name="username" value="xxx" />
     <property name="password" value="xxx" />

步骤 3:为 Seata 创建 undo_log 表

UNDO_LOG 此表用于 Seata 的 AT 模式。

-- Note that when the Seata version is upgraded to 0.3.0+, the normal index will be changed to a unique index.
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;

DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `commodity_code` varchar(255) DEFAULT NULL,
   `count` int(11) DEFAULT 0,
   PRIMARY KEY (`id`),
   UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `user_id` varchar(255) DEFAULT NULL,
   `commodity_code` varchar(255) DEFAULT NULL,
   `count` int(11) DEFAULT 0,
   `money` int(11) DEFAULT 0,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `user_id` varchar(255) DEFAULT NULL,
   `money` int(11) DEFAULT 0,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

步骤 5:启动 Seata-Server 服务

Usage: sh seata-server.sh(for linux and mac) or cmd seata-server.bat(for windows) [options]
   Options:
     --host, -h
       The host to bind.
       Default: 0.0.0.0
     --port, -p
       The port to listen.
       Default: 8091
     --storeMode, -m
       log store mode: file, db
       Default: file
     --help

e.g.

sh seata-server.sh -p 8091 -h 127.0.0.1 -m file

上次修改时间:2023 年 1 月 2 日:增强 en 文档 (#1798) (95a9f4f6c1c)