FESCAR: 快速简单的提交和回滚
FESCAR是什么?
A distributed transaction solution with high performance and ease of use for microservices architecture.
一种高性能、易使用的微服务架构分布式事务解决方案。
在微服务中的分布式事务问题
让我门想象一下一个传统的单体应用,它的业务由三个模块构建而成,他们使用了一个单一本地数据源。
很显然,本地事务可以保证数据一致性
Monolithic App微服务架构中一些事情需要被改变,这三个被上文提及到的模块,被设计为三个不同数据源之上的三个服务,在本地事务能够保证数据一致性。
但是对于真个业务逻辑范围如何保证数据一致性呢?
Microservices ProblemFESCAR是怎么做的?
FESCAR只是一个上述提及问题的解决方案
FESCAR solution首先,如何明确这个分布式事务呢?
我们说,一个分布式事务是一个全局事务,由一批分支事务组成,通常分支事务只是本地事务
Global & BranchFESCAR有三个基础组件:
- Transaction Coordinator(TC): 全局和分支事务的状态的保持,驱动这个全局的提交和回滚.
- Transaction Manager(TM): 明确全局事务的范围:开始一个全局事务,提交或者回滚一个全局事务.
- Resource Manager(RM): 管理分支事务工作资源,告诉TC,注册这个分支事务和上报分支事务的状态,驱动分支事务的的提交和回滚。
一个典型的FESCAR管理分布式事务的生命周期:
-
TM询问TC开启一个新的全局事务,TC生成一个XID,代表这个全局事务
-
XID 通过微服务的调用链传播
-
RM将本地事务注册为XID到TC的相应全局事务的分支。
-
TM要求TC提交或回滚XID的对应的全局事务。
-
TC驱动整个分支在XID对应的全局事务下,去完成分支的提交或者回滚
-
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.
快速开始
让我们开启一个微服务例子
开启FESCAR服务
-
下载正是安装包并解压
-
cd bin,运行启动脚本
sh fester-server.sh /User/min.ji/Downloads/data
用例
用户购买商品的业务逻辑。整个业务逻辑有3个微服务
- 仓储服务:扣减库存数量在给定的商品
- 订单服务:根据购买请求创建订单
- 账户服务:记录账户的余额
架构:
ArchitectureStorageService
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分布式事务解决方案
undefined我们就需要一个注解@GlobalTransactional
在业务方法中
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
......
}
由Dubbo + FESCAR的例子
第一步:设置数据库
- 需求:mysql中的InnoDB引擎
笔记:事实上,在我们使用的例子中,应该有三个服务使用三个这样的数据库,然而,我们可以简单穿件一个数据库,但创建三个数据源。
根据你创建的数据库修改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" />
第二步:创建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
第三步:创建例程业务逻辑表
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;
网友评论