FESCAR: 快速簡單的提交和回滾
FESCAR是什么?
A distributed transaction solution with high performance and ease of use for microservices architecture.
一種高性能、易使用的微服務(wù)架構(gòu)分布式事務(wù)解決方案。
在微服務(wù)中的分布式事務(wù)問題
讓我門想象一下一個傳統(tǒng)的單體應(yīng)用,它的業(yè)務(wù)由三個模塊構(gòu)建而成,他們使用了一個單一本地數(shù)據(jù)源。
很顯然,本地事務(wù)可以保證數(shù)據(jù)一致性

微服務(wù)架構(gòu)中一些事情需要被改變,這三個被上文提及到的模塊,被設(shè)計為三個不同數(shù)據(jù)源之上的三個服務(wù),在本地事務(wù)能夠保證數(shù)據(jù)一致性。
但是對于真?zhèn)€業(yè)務(wù)邏輯范圍如何保證數(shù)據(jù)一致性呢?

FESCAR是怎么做的?
FESCAR只是一個上述提及問題的解決方案

首先,如何明確這個分布式事務(wù)呢?
我們說,一個分布式事務(wù)是一個全局事務(wù),由一批分支事務(wù)組成,通常分支事務(wù)只是本地事務(wù)

FESCAR有三個基礎(chǔ)組件:
- Transaction Coordinator(TC): 全局和分支事務(wù)的狀態(tài)的保持,驅(qū)動這個全局的提交和回滾.
- Transaction Manager(TM): 明確全局事務(wù)的范圍:開始一個全局事務(wù),提交或者回滾一個全局事務(wù).
- Resource Manager(RM): 管理分支事務(wù)工作資源,告訴TC,注冊這個分支事務(wù)和上報分支事務(wù)的狀態(tài),驅(qū)動分支事務(wù)的的提交和回滾。

一個典型的FESCAR管理分布式事務(wù)的生命周期:
TM詢問TC開啟一個新的全局事務(wù),TC生成一個XID,代表這個全局事務(wù)
XID 通過微服務(wù)的調(diào)用鏈傳播
RM將本地事務(wù)注冊為XID到TC的相應(yīng)全局事務(wù)的分支。
TM要求TC提交或回滾XID的對應(yīng)的全局事務(wù)。
TC驅(qū)動整個分支在XID對應(yīng)的全局事務(wù)下,去完成分支的提交或者回滾
TM asks TC to begin a new global transaction. TC generates an XID representing the global transaction.
XID is propagated through microservices' invoke chain.
RM register local transaction as a branch of the corresponding global transaction of XID to TC.
TM asks TC for committing or rollbacking the corresponding global transaction of XID.
TC drives all branch transactions under the corresponding global transaction of XID to finish branch committing or rollbacking.

快速開始
讓我們開啟一個微服務(wù)例子
開啟FESCAR服務(wù)
下載正是安裝包并解壓
-
cd bin,運行啟動腳本
sh fester-server.sh /User/min.ji/Downloads/data
用例
用戶購買商品的業(yè)務(wù)邏輯。整個業(yè)務(wù)邏輯有3個微服務(wù)
- 倉儲服務(wù):扣減庫存數(shù)量在給定的商品
- 訂單服務(wù):根據(jù)購買請求創(chuàng)建訂單
- 賬戶服務(wù):記錄賬戶的余額
架構(gòu):
StorageService
public interface StorageService {
/**
* deduct storage count
*/
void deduct(String commodityCode, int count);
}
OrderService
public interface OrderService {
/**
* create order
*/
Order create(String userId, String commodityCode, int orderCount);
}
AccountService
public interface AccountService {
/**
* debit balance of user's account
*/
void debit(String userId, int money);
}
Main business logic
public class BusinessServiceImpl implements BusinessService {
private StorageService storageService;
private OrderService orderService;
/**
* purchase
*/
public void purchase(String userId, String commodityCode, int orderCount) {
storageService.deduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
}
}
public class OrderServiceImpl implements OrderService {
private OrderDAO orderDAO;
private AccountService accountService;
public Order create(String userId, String commodityCode, int orderCount) {
int orderMoney = calculate(commodityCode, orderCount);
accountService.debit(userId, orderMoney);
Order order = new Order();
order.userId = userId;
order.commodityCode = commodityCode;
order.count = orderCount;
order.money = orderMoney;
// INSERT INTO orders ...
return orderDAO.insert(order);
}
FESCAR分布式事務(wù)解決方案
我們就需要一個注解@GlobalTransactional在業(yè)務(wù)方法中
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
......
}
由Dubbo + FESCAR的例子
第一步:設(shè)置數(shù)據(jù)庫
- 需求:mysql中的InnoDB引擎
筆記:事實上,在我們使用的例子中,應(yīng)該有三個服務(wù)使用三個這樣的數(shù)據(jù)庫,然而,我們可以簡單穿件一個數(shù)據(jù)庫,但創(chuàng)建三個數(shù)據(jù)源。
根據(jù)你創(chuàng)建的數(shù)據(jù)庫修改springXML文檔
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" />
第二步:創(chuàng)建UNDO_LOG表
UNDO_LOG表在FESCAR AT模式中需要被用到
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) 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`),
KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=159 DEFAULT CHARSET=utf8
第三步:創(chuàng)建例程業(yè)務(wù)邏輯表
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;