快速玩转Dubbo生态之一致性事务篇(Seata)
1. 创建实验资源
开始实验之前,您需要先创建ECS实例资源。
1. 在实验室页面,单击创建资源。
2. (可选)在实验室页面左侧导航栏中,单击云产品资源列表,可查看本次实验资源相关信息(例如IP地址、用户信息等)。
说明:资源创建过程需要1~3分钟。
2. Seate背景知识架构详解
本步骤为背景知识学习,详解Seate架构。
Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。
3. Seata背景知识Demo示例架构说明
本步骤为背景知识学习,详解用户采购商品业务使用的微服务代码。
用户采购商品业务,整个业务包含3个微服务:
- 库存服务: 扣减给定商品的库存数量。
public interface StorageService { /** * 扣除存储数量 */ void deduct(String commodityCode, int count); }
- 订单服务: 根据采购请求生成订单。
public interface OrderService { /** * 创建订单 */ Order create(String userId, String commodityCode, int orderCount); }
- 账户服务: 用户账户金额扣减。
public interface AccountService { /** * 从用户账户中借出 */ void debit(String userId, int money); }
4. Seata背景知识Demo示例的主要的业务逻辑代码详解
本步骤为背景知识学习,详解Demo示例的主要的业务逻辑代码。
BusinessServiceImpl:实现采购功能。
public class BusinessServiceImpl implements BusinessService { private StorageService storageService; private OrderService orderService; /** * 采购 */ public void purchase(String userId, String commodityCode, int orderCount) { // 扣除存储数量 storageService.deduct(commodityCode, orderCount); // 创建订单 orderService.create(userId, commodityCode, orderCount); } }
StorageServiceImpl:实现扣减给定商品的库存数量的功能。
public class StorageServiceImpl implements StorageService { private JdbcTemplate jdbcTemplate; @Override public void deduct(String commodityCode, int count) { // 修改数据库:扣减存储数量 jdbcTemplate.update("update storage_tbl set count = count - ? where commodity_code = ?", new Object[]{count, commodityCode}); } }
OrderServiceImpl:实现根据采购请求生成订单的功能。
public class OrderServiceImpl implements OrderService { private AccountService accountService; private JdbcTemplate jdbcTemplate; public Order create(String userId, String commodityCode, int orderCount) { // 计算金额 int orderMoney = calculate(commodityCode, orderCount); // 用户账户中扣减金额 accountService.debit(userId, orderMoney); // 修改数据库:新建订单 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) { // 修改数据库:用户账户中扣减金额 jdbcTemplate.update("update account_tbl set money = money - ? where user_id = ?", new Object[]{money, userId}); } }
5. Demo启动实践
本实验场景提供示例代码和相关环境,本步骤指导您如何快速启动示例。
- 依次执行如下命令,通过docker-compose启动Seata-Server和MySQL等。
cd /root/dubbo-samples/dubbo-samples-transaction/src/main/resources/docker systemctl enable docker systemctl start docker docker-compose up
- 在实验页面右上角,单击图标,新建一个终端窗口二。
- 在终端窗口二中,执行如下maven命令,打包Demo工程。
cd /root/dubbo-samples/dubbo-samples-transaction/ mvn clean package
返回结果如下,表示打包成功。
- 在终端窗口二中,执行如下命令,启动AccountService。
cd /root/dubbo-samples/dubbo-samples-transaction/ java -classpath ./target/dubbo-samples-transaction-1.0-SNAPSHOT.jar org.apache.dubbo.samples.starter.DubboAccountServiceStarter
返回结果如下,表示AccountService启动成功。
- 在实验页面右上角,单击图标,新建一个终端窗口三。
- 在终端窗口三中,执行如下命令,启动OrderService。
cd /root/dubbo-samples/dubbo-samples-transaction/ java -classpath ./target/dubbo-samples-transaction-1.0-SNAPSHOT.jar org.apache.dubbo.samples.starter.DubboOrderServiceStarter
返回结果如下,表示OrderService启动成功。
- 在实验页面右上角,单击图标,新建一个终端窗口四。
- 执行如下命令,启动 StorageService。
cd /root/dubbo-samples/dubbo-samples-transaction/ java -classpath ./target/dubbo-samples-transaction-1.0-SNAPSHOT.jar org.apache.dubbo.samples.starter.DubboStorageServiceStarter
- 在实验页面右上角,单击图标,新建一个终端窗口五。
- 执行如下命令, 启动 StorageService。
cd /root/dubbo-samples/dubbo-samples-transaction/ java -classpath ./target/dubbo-samples-transaction-1.0-SNAPSHOT.jar org.apache.dubbo.samples.starter.DubboBusinessTester
6. Demo核心流程详解
修改业务代码。
此处仅仅需要一行注解 @GlobalTransactional 写在业务发起方的方法上,例如示例代码:
@GlobalTransactional public void purchase(String userId, String commodityCode, int orderCount) { ...... }
安装数据库。
要求: MySQL (InnoDB 存储引擎)。
说明:在本场景的示例中3个微服务需要3个独立的数据库,但为了方便我们使用同一物理库并配置3个逻辑连接串。
需要更改以下dubbo-account-service.xml、dubbo-order-service.xml和dubbo-storage-service.xml文件中的数据库url、username和password。
<property name="url" value="jdbc:mysql://x.x.x.x:3306/xxx" /> <property name="username" value="xxx" /> <property name="password" value="xxx" />
为Seata创建undo_log表。undo_log此表用于Seata的AT模式。
-- 注意当 Seata 版本升级至 0.3.0+ 将由之前的普通索引变更为唯一索引。 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;
启动Seata-Server服务。
下载Seata软件包,并将其解压缩。
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
实验地址:https://developer.aliyun.com/adc/scenario/1ab92d77e58445ae8e82678e89c1f12f