之前我們已經在博客《分佈式事務--Fescar》中瞭解學習到Fescar相關的架構,接下來我們分別用幾篇博客分別來介紹一下Fescar的 TM、RM 和 TC之間的交互流程。
TM、RM和TC之間的交互流程圖:
簡單角色理解:
TC: Fesacr-server應用
TM:dubbo服務調用方
RM:dubbo服務提供方
一、示例
官方示例:https://github.com/alibaba/fescar/wiki/Quick-Start
調用方:
ublic class BusinessServiceImpl implements BusinessService {
private static final Logger LOGGER = LoggerFactory.getLogger(BusinessService.class);
private StorageService storageService;
private OrderService orderService;
@Override
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
storageService.deduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
System.out.println("hello fescar");
}
public void setStorageService(StorageService storageService) {
this.storageService = storageService;
}
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"dubbo-business.xml"});
final BusinessService business = (BusinessService)context.getBean("business");
LOGGER.info("Main business begin ... xid: " + RootContext.getXID());
business.purchase("U100001", "C00321", 2);
LOGGER.info("Main business end ... xid: " + RootContext.getXID());
}
}
在示例代碼中我們看到了註解@GlobalTransactional,註解的方法:
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
storageService.deduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
System.out.println("hello fescar");
}
這樣我們可以猜測到在真正執行purchase方法前後Fescar肯定做了很多處理操作,當然Fescar也是利用Spring AOP來實現的,實現類爲GlobalTransactionalInterceptor,在GlobalTransactionalInterceptor中我們可以看到如下操作:
@Override
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
final GlobalTransactional anno = getAnnotation(methodInvocation.getMethod());
if (anno != null) {
try {
return transactionalTemplate.execute(new TransactionalExecutor() {
@Override
public Object execute() throws Throwable {
return methodInvocation.proceed();
}
@Override
public int timeout() {
return anno.timeoutMills();
}
@Override
public String name() {
if (anno.name() != null) {
return anno.name();
}
return formatMethod(methodInvocation.getMethod());
}
});
} catch (TransactionalExecutor.ExecutionException e) {
TransactionalExecutor.Code code = e.getCode();
switch (code) {
case RollbackDone:
throw e.getOriginalException();
case BeginFailure:
failureHandler.onBeginFailure(e.getTransaction(), e.getCause());
throw e.getCause();
case CommitFailure:
failureHandler.onCommitFailure(e.getTransaction(), e.getCause());
throw e.getCause();
case RollbackFailure:
failureHandler.onRollbackFailure(e.getTransaction(), e.getCause());
throw e.getCause();
default:
throw new ShouldNeverHappenException("Unknown TransactionalExecutor.Code: " + code);
}
}
}
return methodInvocation.proceed();
}
在真正調用方法之前首先調用了TransactionalTemplate的execute方法,在execute中主要調用了一下操作:
(1)tx.begin(business.timeout(), business.name()); 啓動事務,在這裏面的操作就是想TC(Fesacr-server應用)申請一個全局的事物XID
(2)rs = business.execute();,調用本地方法,當調用遠程dubbo服務時會利用dubbo的Filter機制將xid傳遞到RM服務中
(3)tx.rollback();調用異常時事務回滾,會向TC發送全局事務XID,回滾所有RM的事務。
(4)tx.commit(); 事務提交,會向TC發送全局事物XID,提交所有RM的事務。
通過以上過程分析Fescar將分佈式事務在TM層做了抽象處理,簡化爲單一事務,只需要開啓事務、調用、提交事務或回滾,並且事務開啓、提交或回滾只需要將操作提交到TC即可,由TC對象去管理各個RM的分片事務。
public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {
// 1. get or create a transaction
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
// 2. begin transaction
try {
tx.begin(business.timeout(), business.name());
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.BeginFailure);
}
Object rs = null;
try {
// Do Your Business
rs = business.execute();
} catch (Throwable ex) {
// 3. any business exception, rollback.
try {
tx.rollback();
// 3.1 Successfully rolled back
throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);
} catch (TransactionException txe) {
// 3.2 Failed to rollback
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.RollbackFailure, ex);
}
}
// 4. everything is fine, commit.
try {
tx.commit();
} catch (TransactionException txe) {
// 4.1 Failed to commit
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.CommitFailure);
}
return rs;
}
begin、rollback和commit方法最終在DefaultTransactionManager中遠程調用將指令發送到TC(Fesacr-server應用)中。
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout) throws TransactionException {
GlobalBeginRequest request = new GlobalBeginRequest();
request.setTransactionName(name);
request.setTimeout(timeout);
GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
return response.getXid();
}
@Override
public GlobalStatus commit(String xid) throws TransactionException {
long txId = XID.getTransactionId(xid);
GlobalCommitRequest globalCommit = new GlobalCommitRequest();
globalCommit.setTransactionId(txId);
GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit);
return response.getGlobalStatus();
}
@Override
public GlobalStatus rollback(String xid) throws TransactionException {
long txId = XID.getTransactionId(xid);
GlobalRollbackRequest globalRollback = new GlobalRollbackRequest();
globalRollback.setTransactionId(txId);
GlobalRollbackResponse response = (GlobalRollbackResponse) syncCall(globalRollback);
return response.getGlobalStatus();
}
在syncCall方法中,begin、rollback和commit是同樣的參數,不過是給TC發送的一個狀態指令
private AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException {
try {
return (AbstractTransactionResponse) TmRpcClient.getInstance().sendMsgWithResponse(request);
} catch (TimeoutException toe) {
throw new TransactionException(TransactionExceptionCode.IO, toe);
}
}
目前Fescar只支持Dubbo作爲RPC調用,實現分佈式事務,TM和RM之間全局事務XID中傳遞目前是利用dubbo的Filter來實現的,這樣每個RM的分片事務有一個統一的事務XID,這樣dubbo的服務提供者(RM對象)在被RPC調用時可以獲取全局事務XID。
@Activate(group = { Constants.PROVIDER, Constants.CONSUMER }, order = 100)
public class TransactionPropagationFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String xid = RootContext.getXID();
String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]");
}
boolean bind = false;
if (xid != null) {
RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);
} else {
if (rpcXid != null) {
RootContext.bind(rpcXid);
bind = true;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("bind[" + rpcXid + "] to RootContext");
}
}
}
try {
return invoker.invoke(invocation);
} finally {
if (bind) {
String unbindXid = RootContext.unbind();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("unbind[" + unbindXid + "] from RootContext");
}
if (!rpcXid.equalsIgnoreCase(unbindXid)) {
LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid);
if (unbindXid != null) {
RootContext.bind(unbindXid);
LOGGER.warn("bind [" + unbindXid + "] back to RootContext");
}
}
}
}
}
}
簡單流程圖: