之前我們已經用兩篇博客分別介紹了Fescar中的TM和RM兩個角色的相關操作,這篇博客我們來介紹一下TC
一、簡介
TC(Fescar Server)作爲全局事務協調器主要做了以下操作。
(1)TM和RM啓動時會註冊到TC,TC會將註冊信息持久化。
(2)TM在begin事務會首先向TC申請獲取全局事務xid。
(3)RM在執行數據庫事務之前首先向TC申請獲取分片事務branchId。
(4)TM進行事務commit或rollback時會將根據全局事務xid提交到TC,TC根據全局事務xid分別通知分片事務RM,TC 調度 XID 下管轄的全部分支事務完成提交或回滾請求。
二、源碼分析
1、接收請求
Fescar提供 了RpcServer用來接收所有的請求並進行處理。
(1)服務註冊請求由ServerMessageListener的onRegRmMessage方法處理
(2)與事務管理相關的請求由ServerMessageListener.onTrxMessage方法處理
@Override
public void dispatch(long msgId, ChannelHandlerContext ctx, Object msg) {
if (msg instanceof RegisterRMRequest) {
//服務註冊請求
serverMessageListener.onRegRmMessage(msgId, ctx, (RegisterRMRequest)msg, this,
checkAuthHandler);
} else {
if (ChannelManager.isRegistered(ctx.channel())) {
//事務管理請求
serverMessageListener.onTrxMessage(msgId, ctx, msg, this);
} else {
try {
closeChannelHandlerContext(ctx);
} catch (Exception exx) {
LOGGER.error(exx.getMessage());
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info(String.format("close a unhandled connection! [%s]", ctx.channel().toString()));
}
}
}
}
2、服務註冊處理
服務註冊由ServerMessageListener的實現類DefaultServerMessageListenerImpl的onRegRmMessage或registerTMChannel方法處理,最終RM和TM信息都添加到Map中。
RM:dbkey+appname+ip port context
/**
* dbkey+appname+ip port context
*/
private static final ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<Integer,
RpcContext>>>>
RM_CHANNELS
= new ConcurrentHashMap<String, ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<Integer,
RpcContext>>>>();
TM:ip+appname,port
/**
* ip+appname,port
*/
private static final ConcurrentMap<String, ConcurrentMap<Integer, RpcContext>> TM_CHANNELS
= new ConcurrentHashMap<String, ConcurrentMap<Integer, RpcContext>>();
服務註冊還是比較簡單的,就是將Channel信息添加到Map中
/**
* Register tm channel.
*
* @param request the request
* @param channel the channel
* @throws IncompatibleVersionException the incompatible version exception
*/
public static void registerTMChannel(RegisterTMRequest request, Channel channel)
throws IncompatibleVersionException {
Version.checkVersion(request.getVersion());
RpcContext rpcContext = buildChannelHolder(TransactionRole.TMROLE, request.getVersion(),
request.getApplicationId(),
request.getTransactionServiceGroup(),
null, channel);
rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS);
String clientIdentified = rpcContext.getApplicationId() + Constants.CLIENT_ID_SPLIT_CHAR
+ getClientIpFromChannel(channel);
TM_CHANNELS.putIfAbsent(clientIdentified, new ConcurrentHashMap<Integer, RpcContext>());
ConcurrentMap<Integer, RpcContext> clientIdentifiedMap = TM_CHANNELS.get(clientIdentified);
rpcContext.holdInClientChannels(clientIdentifiedMap);
}
/**
* Register rm channel.
*
* @param resourceManagerRequest the resource manager request
* @param channel the channel
* @throws IncompatibleVersionException the incompatible version exception
*/
public static void registerRMChannel(RegisterRMRequest resourceManagerRequest, Channel channel)
throws IncompatibleVersionException {
Version.checkVersion(resourceManagerRequest.getVersion());
Set<String> dbkeySet = dbKeytoSet(resourceManagerRequest.getResourceIds());
RpcContext rpcContext;
if (!IDENTIFIED_CHANNELS.containsKey(channel)) {
rpcContext = buildChannelHolder(TransactionRole.RMROLE, resourceManagerRequest.getVersion(),
resourceManagerRequest.getApplicationId(), resourceManagerRequest.getTransactionServiceGroup(),
resourceManagerRequest.getResourceIds(), channel);
rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS);
} else {
rpcContext = IDENTIFIED_CHANNELS.get(channel);
rpcContext.addResources(dbkeySet);
}
if (null == dbkeySet || dbkeySet.isEmpty()) { return; }
for (String resourceId : dbkeySet) {
RM_CHANNELS.putIfAbsent(resourceId,
new ConcurrentHashMap<String, ConcurrentMap<String, ConcurrentMap<Integer, RpcContext>>>());
ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<Integer, RpcContext>>> applicationIdMap
= RM_CHANNELS.get(resourceId);
applicationIdMap.putIfAbsent(resourceManagerRequest.getApplicationId(),
new ConcurrentHashMap<String, ConcurrentMap<Integer, RpcContext>>());
ConcurrentMap<String, ConcurrentMap<Integer, RpcContext>> clientIpMap = applicationIdMap.get(
resourceManagerRequest.getApplicationId());
String clientIp = getClientIpFromChannel(channel);
clientIpMap.putIfAbsent(clientIp, new ConcurrentHashMap<Integer, RpcContext>());
ConcurrentMap<Integer, RpcContext> portMap = clientIpMap.get(clientIp);
rpcContext.holdInResourceManagerChannels(resourceId, portMap);
updateChannelsResource(resourceId, clientIp, resourceManagerRequest.getApplicationId());
}
}
3、begin操作
TM在執行dubbo遠程調用之前會調用begin方法向TC申請一個全局事務xid,TC由GlobalBeginRequest的handle方法進行處理
@Override
public AbstractTransactionResponse handle(RpcContext rpcContext) {
return handler.handle(this, rpcContext);
}
在AbstractTCInboundHandler中調用handle處理操作。
@Override
public GlobalBeginResponse handle(GlobalBeginRequest request, final RpcContext rpcContext) {
GlobalBeginResponse response = new GlobalBeginResponse();
exceptionHandleTemplate(new Callback<GlobalBeginRequest, GlobalBeginResponse>() {
@Override
public void execute(GlobalBeginRequest request, GlobalBeginResponse response) throws TransactionException {
doGlobalBegin(request, response, rpcContext);
}
}, request, response);
return response;
}
在DefaultCoordinator中執行doGlobalBegin操作,最終調用DefaultCore的begin方法
@Override
protected void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response, RpcContext rpcContext) throws TransactionException {
response.setXid(core.begin(rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(), request.getTransactionName(), request.getTimeout()));
}
在DefaultCore的begin方法中完成全局事務xid的生成
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout) throws TransactionException {
GlobalSession session = GlobalSession.createGlobalSession(
applicationId, transactionServiceGroup, name, timeout);
session.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
//全局xid會進行持久化操作
session.begin();
return XID.generateXID(session.getTransactionId());
}
這樣就生成全局事務xid了。
4、commit操作
TM在進行commit操作後最終也是有TC的DefaultCore的commit方法進行操作,根據xid找到GlobalSession,然後依次調用分片事務進行commit操作。
@Override
public GlobalStatus commit(String xid) throws TransactionException {
GlobalSession globalSession = SessionHolder.findGlobalSession(XID.getTransactionId(xid));
if (globalSession == null) {
return GlobalStatus.Finished;
}
GlobalStatus status = globalSession.getStatus();
globalSession.closeAndClean(); // Highlight: Firstly, close the session, then no more branch can be registered.
if (status == GlobalStatus.Begin) {
if (globalSession.canBeCommittedAsync()) {
asyncCommit(globalSession);
} else {
doGlobalCommit(globalSession, false);
}
}
return globalSession.getStatus();
}
@Override
public void doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException {
//查找所有的分片事務,依次進行commit操作
for (BranchSession branchSession : globalSession.getSortedBranches()) {
BranchStatus currentStatus = branchSession.getStatus();
if (currentStatus == BranchStatus.PhaseOne_Failed) {
continue;
}
try {
BranchStatus branchStatus = resourceManagerInbound.branchCommit(XID.generateXID(branchSession.getTransactionId()), branchSession.getBranchId(),
branchSession.getResourceId(), branchSession.getApplicationData());
switch (branchStatus) {
case PhaseTwo_Committed:
globalSession.removeBranch(branchSession);
continue;
case PhaseTwo_CommitFailed_Unretriable:
if (globalSession.canBeCommittedAsync()) {
LOGGER.error("By [" + branchStatus + "], failed to commit branch " + branchSession);
continue;
} else {
globalSession.changeStatus(GlobalStatus.CommitFailed);
globalSession.end();
LOGGER.error("Finally, failed to commit global[" + globalSession.getTransactionId() + "] since branch[" + branchSession.getBranchId() + "] commit failed");
return;
}
default:
if (!retrying) {
queueToRetryCommit(globalSession);
return;
}
if (globalSession.canBeCommittedAsync()) {
LOGGER.error("By [" + branchStatus + "], failed to commit branch " + branchSession);
continue;
} else {
LOGGER.error("Failed to commit global[" + globalSession.getTransactionId() + "] since branch[" + branchSession.getBranchId() + "] commit failed, will retry later.");
return;
}
}
} catch (Exception ex) {
LOGGER.info("Exception committing branch " + branchSession, ex);
if (!retrying) {
queueToRetryCommit(globalSession);
if (ex instanceof TransactionException) {
throw (TransactionException) ex;
} else {
throw new TransactionException(ex);
}
}
}
}
if (globalSession.hasBranch()) {
LOGGER.info("Global[" + globalSession.getTransactionId() + "] committing is NOT done.");
return;
}
globalSession.changeStatus(GlobalStatus.Committed);
globalSession.end();
LOGGER.info("Global[" + globalSession.getTransactionId() + "] committing is successfully done.");
}
5、rollback操作
rollback與commit的操作幾乎類似,也是依次調用分片事務的rollback操作
@Override
public GlobalStatus rollback(String xid) throws TransactionException {
GlobalSession globalSession = SessionHolder.findGlobalSession(XID.getTransactionId(xid));
if (globalSession == null) {
return GlobalStatus.Finished;
}
GlobalStatus status = globalSession.getStatus();
globalSession.close(); // Highlight: Firstly, close the session, then no more branch can be registered.
if (status == GlobalStatus.Begin) {
globalSession.changeStatus(GlobalStatus.Rollbacking);
doGlobalRollback(globalSession, false);
}
return globalSession.getStatus();
}
@Override
public void doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException {
for (BranchSession branchSession : globalSession.getReverseSortedBranches()) {
BranchStatus currentBranchStatus = branchSession.getStatus();
if (currentBranchStatus == BranchStatus.PhaseOne_Failed) {
continue;
}
try {
BranchStatus branchStatus = resourceManagerInbound.branchRollback(XID.generateXID(branchSession.getTransactionId()), branchSession.getBranchId(),
branchSession.getResourceId(), branchSession.getApplicationData());
switch (branchStatus) {
case PhaseTwo_Rollbacked:
globalSession.removeBranch(branchSession);
LOGGER.error("Successfully rolled back branch " + branchSession);
continue;
case PhaseTwo_RollbackFailed_Unretriable:
GlobalStatus currentStatus = globalSession.getStatus();
if (currentStatus.name().startsWith("Timeout")) {
globalSession.changeStatus(GlobalStatus.TimeoutRollbackFailed);
} else {
globalSession.changeStatus(GlobalStatus.RollbackFailed);
}
globalSession.end();
LOGGER.error("Failed to rollback global[" + globalSession.getTransactionId() + "] since branch[" + branchSession.getBranchId() + "] rollback failed");
return;
default:
LOGGER.info("Failed to rollback branch " + branchSession);
if (!retrying) {
queueToRetryRollback(globalSession);
}
return;
}
} catch (Exception ex) {
LOGGER.info("Exception rollbacking branch " + branchSession, ex);
if (!retrying) {
queueToRetryRollback(globalSession);
if (ex instanceof TransactionException) {
throw (TransactionException) ex;
} else {
throw new TransactionException(ex);
}
}
}
}
GlobalStatus currentStatus = globalSession.getStatus();
if (currentStatus.name().startsWith("Timeout")) {
globalSession.changeStatus(GlobalStatus.TimeoutRollbacked);
} else {
globalSession.changeStatus(GlobalStatus.Rollbacked);
}
globalSession.end();
}
總結:
(1)TC作爲一個註冊中心,保存了TM和RM服務信息。
(2)TC提供全局事務xid和分片事務branchId的創建操作。
(3)TC轉發TM的commit或rollback相關操作。