交易管理
此範例示範如何透過 Seata 實現分散式 Dubbo 服務的交易管理,以確保資料一致性。
什麼是 Seata
Seata 是一個開源的分散式交易解決方案,致力於提供高效能且易於使用的分散式交易服務。Seata 將為用戶提供 AT、TCC、SAGA 和 XA 交易模式,為用戶打造一站式分散式解決方案。
1. 範例架構說明
用戶購買商品業務,整個業務包含 3 個微服務
- 庫存服務:扣除指定商品的庫存數量。
- 訂單服務:根據購買請求產生訂單。
- 帳戶服務:扣除用戶帳戶金額。
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. 範例核心流程
步驟 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;
步驟 4:建立相關業務表
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 日:增強英文文件 (#1798) (95a9f4f6c1c)