TX-LCN中的LCN模式是通過代理數據庫連接進而對事物進行控制的。通過靜態代理的方式包裝的原本來的connection,設置爲手動提交,根據事物狀態控制提交與回滾。
所以如果用LCN模式的分佈式事務,必須有本地連接,即要操作本地數據庫。(當然也可以多種模式混合使用)
代理連接如下,state爲事務狀態,1爲分佈式事務成功本地鏈接提交,其他爲失敗進行本地鏈接回滾
public class LcnConnectionProxy implements Connection {
private Connection connection;
public LcnConnectionProxy(Connection connection) {
this.connection = connection;
}
/**
* notify connection
*
* @param state transactionState
* @return RpcResponseState RpcResponseState
*/
public RpcResponseState notify(int state) {
try {
if (state == 1) {
log.debug("commit transaction type[lcn] proxy connection:{}.", this);
connection.commit();
} else {
log.debug("rollback transaction type[lcn] proxy connection:{}.", this);
connection.rollback();
}
connection.close();
log.debug("transaction type[lcn] proxy connection:{} closed.", this);
return RpcResponseState.success;
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
return RpcResponseState.fail;
}
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
connection.setAutoCommit(false);
}
代理連接的獲取是用Aop實現的
public class DataSourceAspect implements Ordered {
private final TxClientConfig txClientConfig;
private final DTXResourceWeaver dtxResourceWeaver;
public DataSourceAspect(TxClientConfig txClientConfig, DTXResourceWeaver dtxResourceWeaver) {
this.txClientConfig = txClientConfig;
this.dtxResourceWeaver = dtxResourceWeaver;
}
//環繞通知獲取代理連接
@Around("execution(* javax.sql.DataSource.getConnection(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
return dtxResourceWeaver.getConnection(() -> (Connection) point.proceed());
}
}
public class DTXResourceWeaver {
private final TxLcnBeanHelper txLcnBeanHelper;
public DTXResourceWeaver(TxLcnBeanHelper txLcnBeanHelper) {
this.txLcnBeanHelper = txLcnBeanHelper;
}
public Object getConnection(ConnectionCallback connectionCallback) throws Throwable {
//事務本地上下文
DTXLocalContext dtxLocalContext = DTXLocalContext.cur();
//如果設置爲代理模式
if (Objects.nonNull(dtxLocalContext) && dtxLocalContext.isProxy()) {
String transactionType = dtxLocalContext.getTransactionType();
//根據事物類型獲取代理器
TransactionResourceProxy resourceProxy = txLcnBeanHelper.loadTransactionResourceProxy(transactionType);
//構造代理連接對象
Connection connection = resourceProxy.proxyConnection(connectionCallback);
log.debug("proxy a sql connection: {}.", connection);
return connection;
}
//如果不是代理直接返回原始連接
return connectionCallback.call();
}
}
以上是一些鋪墊
下面介紹整體流程,例子以TX-LCN分佈式事務框架應用與解析-2例子爲標準進行講解
在例子中我們就在調用接口的地方加上了一個註解@LcnTransaction,這是一個入口整個流程的開始就在這裏。
TransactionAspect類是一個切面類,對@LcnTransaction註解設置了環繞通知,在碰到註解@LcnTransaction開始了整個流程
public class TransactionAspect implements Ordered {
/**
* DTC Aspect (Type of LCN)
*/
@Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.LcnTransaction)")
public void lcnTransactionPointcut() {
}
@Around("lcnTransactionPointcut() && !txcTransactionPointcut()" +
"&& !tccTransactionPointcut() && !txTransactionPointcut()")
public Object runWithLcnTransaction(ProceedingJoinPoint point) throws Throwable {
//根據ProceedingJoinPoint 從緩存中獲取事務信息
DTXInfo dtxInfo = DTXInfo.getFromCache(point);
//獲取註解
LcnTransaction lcnTransaction = dtxInfo.getBusinessMethod().getAnnotation(LcnTransaction.class);
//設置事務類型爲LCN
dtxInfo.setTransactionType(Transactions.LCN);
//設置事務傳播行爲,發起方是REQUIRED,參與方式是SUPPORTS
dtxInfo.setTransactionPropagation(lcnTransaction.propagation());
//開始執行事務
return dtxLogicWeaver.runTransaction(dtxInfo, point::proceed);
}
1、在註解的開始從緩存中獲取了事務信息
這裏做了個緩存,當再次執行這個方法時則直接取得事務信息。這個方法的作用就是根據當前要執行的類名、方法名和方法參數構造事務信息對象,存入緩存。
public static DTXInfo getFromCache(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//簽名
String signature = proceedingJoinPoint.getSignature().toString();
//根據簽名構造事務單元
String unitId = Transactions.unitId(signature);
//根據事務單元獲取事務信息
DTXInfo dtxInfo = dtxInfoCache.get(unitId);
if (Objects.isNull(dtxInfo)) {
//獲取簽名
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
//獲取要執行的方法,這是接口的方法並不是我們要的類
Method method = methodSignature.getMethod();
//獲取目標方法
Class<?> targetClass = proceedingJoinPoint.getTarget().getClass();
//這纔是我們要的真實方法
Method thisMethod = targetClass.getMethod(method.getName(), method.getParameterTypes());
//構造
dtxInfo = new DTXInfo(thisMethod, proceedingJoinPoint.getArgs(), targetClass);
//緩存起來
dtxInfoCache.put(unitId, dtxInfo);
}
dtxInfo.reanalyseMethodArgs(proceedingJoinPoint.getArgs());
return dtxInfo;
}
2、事務執行runTransaction方法
public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable {
if (Objects.isNull(DTXLocalContext.cur())) {
DTXLocalContext.getOrNew();
} else {
return business.call();
}
log.debug("<---- TxLcn start ---->");
DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew();
TxContext txContext;
// ---------- 保證每個模塊在一個DTX下只會有一個TxContext ---------- //
if (globalContext.hasTxContext()) {
// 有事務上下文的獲取父上下文
txContext = globalContext.txContext();
dtxLocalContext.setInGroup(true);
log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId());
} else {
// 沒有的開啓本地事務上下文
txContext = globalContext.startTx();
}
// 本地事務調用
if (Objects.nonNull(dtxLocalContext.getGroupId())) {
dtxLocalContext.setDestroy(false);
}
dtxLocalContext.setUnitId(dtxInfo.getUnitId());
dtxLocalContext.setGroupId(txContext.getGroupId());
dtxLocalContext.setTransactionType(dtxInfo.getTransactionType());
// 事務參數
TxTransactionInfo info = new TxTransactionInfo();
info.setBusinessCallback(business);
info.setGroupId(txContext.getGroupId());
info.setUnitId(dtxInfo.getUnitId());
info.setPointMethod(dtxInfo.getBusinessMethod());
info.setPropagation(dtxInfo.getTransactionPropagation());
info.setTransactionInfo(dtxInfo.getTransactionInfo());
info.setTransactionType(dtxInfo.getTransactionType());
info.setTransactionStart(txContext.isDtxStart());
//LCN事務處理器
try {
return transactionServiceExecutor.transactionRunning(info);
} finally {
// 線程執行業務完畢清理本地數據
if (dtxLocalContext.isDestroy()) {
// 通知事務執行完畢
synchronized (txContext.getLock()) {
txContext.getLock().notifyAll();
}
// TxContext生命週期是? 和事務組一樣(不與具體模塊相關的)
if (!dtxLocalContext.isInGroup()) {
globalContext.destroyTx();
}
DTXLocalContext.makeNeverAppeared();
TracingContext.tracing().destroy();
}
log.debug("<---- TxLcn end ---->");
}
}
方法比較長,但是邏輯比較簡單
1、獲取或新建一個線程變量DTXLocalContext
2、如果已經存在事務上下文則獲取,否則構建事務上下文
3、爲DTXLocalContext設置參數
4、構造TxTransactionInfo 並設置參數
5、執行事務
6、finally做一些通知與銷燬操作。
其他還好,主要說下第二點什麼情況下要獲取已有的TxContext ,什麼時候構造新的
如果一個調用鏈是這樣的A->B->C 三個都是不同的模塊則會爲每個模塊都構造新的TxContext,保證一個模塊一個
如果三個是兩個模塊,即A是一個模塊,B和C是一個模塊根據調用鏈的情況肯定是C的用B的TxContext。
鑑於本例中都是單獨的模塊所以對於每一個模塊來說都是新創建TxContext
public TxContext startTx() {
TxContext txContext = new TxContext();
// 事務發起方判斷
txContext.setDtxStart(!TracingContext.tracing().hasGroup());
//是否是事務發起方
if (txContext.isDtxStart()) {
//是的話開始事務組,主要是構造事務id
TracingContext.tracing().beginTransactionGroup();
}
//設置事務組id
txContext.setGroupId(TracingContext.tracing().groupId());
String txContextKey = txContext.getGroupId() + ".dtx";
//key與txContext緩存起來
attachmentCache.attach(txContextKey, txContext);
log.debug("Start TxContext[{}]", txContext.getGroupId());
return txContext;
}
對於本例中a是事務發起方,其他的兩個是事務參與方
第6點雖然東西不多但是有必要講下
// 線程執行業務完畢清理本地數據
if (dtxLocalContext.isDestroy()) {
// 通知事務執行完畢
synchronized (txContext.getLock()) {
txContext.getLock().notifyAll();
}
// TxContext生命週期是? 和事務組一樣(不與具體模塊相關的)
if (!dtxLocalContext.isInGroup()) {
globalContext.destroyTx();
}
DTXLocalContext.makeNeverAppeared();
TracingContext.tracing().destroy();
}
public void destroyTx() {
if (!hasTxContext()) {
throw new IllegalStateException("non TxContext.");
}
destroyTx(txContext().getGroupId());
}
public void destroyTx(String groupId) {
attachmentCache.remove(groupId + ".dtx");
log.debug("Destroy TxContext[{}]", groupId);
}
業務執行完會把attachmentCache的key移除掉,後面在參與方clean事務的時候纔不會await。
3、transactionRunning方法執行事務
public Object transactionRunning(TxTransactionInfo info) throws Throwable {
// 1. 獲取事務類型,LCN
String transactionType = info.getTransactionType();
// 2. 獲取事務傳播狀態
DTXPropagationState propagationState = propagationResolver.resolvePropagationState(info);
// 2.1 如果不參與分佈式事務立即終止
if (propagationState.isIgnored()) {
return info.getBusinessCallback().call();
}
// 3. 獲取本地分佈式事務控制器
DTXLocalControl dtxLocalControl = txLcnBeanHelper.loadDTXLocalControl(transactionType, propagationState);
// 4. 織入事務操作
try {
// 4.1 記錄事務類型到事務上下文
Set<String> transactionTypeSet = globalContext.txContext(info.getGroupId()).getTransactionTypes();
transactionTypeSet.add(transactionType);
//執行前
dtxLocalControl.preBusinessCode(info);
// 4.2 業務執行前
txLogger.txTrace(
info.getGroupId(), info.getUnitId(), "pre business code, unit type: {}", transactionType);
// 4.3 執行業務
Object result = dtxLocalControl.doBusinessCode(info);
// 4.4 業務執行成功
txLogger.txTrace(info.getGroupId(), info.getUnitId(), "business success");
dtxLocalControl.onBusinessCodeSuccess(info, result);
return result;
} catch (TransactionException e) {
txLogger.error(info.getGroupId(), info.getUnitId(), "before business code error");
throw e;
} catch (Throwable e) {
// 4.5 業務執行失敗
txLogger.error(info.getGroupId(), info.getUnitId(), Transactions.TAG_TRANSACTION,
"business code error");
dtxLocalControl.onBusinessCodeError(info, e);
throw e;
} finally {
// 4.6 業務執行完畢
dtxLocalControl.postBusinessCode(info);
}
}
事務控制器根據事物類型與傳播狀態執行的也不同
獲取傳播類型,發起方是create,參與方是join
public DTXPropagationState resolvePropagationState(TxTransactionInfo txTransactionInfo) throws TransactionException {
// 本地已在DTX,根據事務傳播,靜默加入
if (DTXLocalContext.cur().isInGroup()) {
log.info("SILENT_JOIN group!");
return DTXPropagationState.SILENT_JOIN;
}
// 發起方之前沒有事務
if (txTransactionInfo.isTransactionStart()) {
// 根據事務傳播,對於 SUPPORTS 不參與事務
if (DTXPropagation.SUPPORTS.equals(txTransactionInfo.getPropagation())) {
return DTXPropagationState.NON;
}
// 根據事務傳播,創建事務
return DTXPropagationState.CREATE;
}
// 已經存在DTX,根據事務傳播,加入
return DTXPropagationState.JOIN;
}
事務發起方控制器是LcnStartingTransaction事務參與方控制器是LcnRunningTransaction
這裏分爲四步執行業務前、執行業務,執行業務後、最後finally。
1、執行業務前preBusinessCode方法
1)發起方。
通過netty調用服務端創建事務組
設置DTXLocalContext的proxy爲true。
public void preBusinessCode(TxTransactionInfo info) throws TransactionException {
// create DTX group
transactionControlTemplate.createGroup(
info.getGroupId(), info.getUnitId(), info.getTransactionInfo(), info.getTransactionType());
// lcn type need connection proxy
DTXLocalContext.makeProxy();
}
public void createGroup(String groupId, String unitId, TransactionInfo transactionInfo, String transactionType)
throws TransactionException {
//創建事務組
try {
// 日誌
txLogger.txTrace(groupId, unitId,
"create group > transaction type: {}", transactionType);
// 創建事務組消息
reliableMessenger.createGroup(groupId);
// 緩存發起方切面信息
aspectLogger.trace(groupId, unitId, transactionInfo);
} catch (RpcException e) {
// 通訊異常
dtxExceptionHandler.handleCreateGroupMessageException(groupId, e);
} catch (LcnBusinessException e) {
// 創建事務組業務失敗
dtxExceptionHandler.handleCreateGroupBusinessException(groupId, e.getCause());
}
txLogger.txTrace(groupId, unitId, "create group over");
}
public void createGroup(String groupId) throws RpcException, LcnBusinessException {
// TxManager創建事務組
MessageDto messageDto = request(MessageCreator.createGroup(groupId));
if (!MessageUtils.statusOk(messageDto)) {
throw new LcnBusinessException(messageDto.loadBean(Throwable.class));
}
}
服務端接收到創建事務組信息後會用CreateGroupExecuteService的execute方法進行處理
public Serializable execute(TransactionCmd transactionCmd) throws TxManagerException {
try {
transactionManager.begin(transactionCmd.getGroupId());
} catch (TransactionException e) {
throw new TxManagerException(e);
}
txLogger.txTrace(transactionCmd.getGroupId(), null, "created group");
return null;
}
public void begin(String groupId) throws TransactionException {
try {
dtxContextRegistry.create(groupId);
} catch (TransactionException e) {
throw new TransactionException(e);
}
}
public DTXContext create(String groupId) throws TransactionException {
try {
fastStorage.initGroup(groupId);
} catch (FastStorageException e) {
// idempotent processing
if (e.getCode() != FastStorageException.EX_CODE_REPEAT_GROUP) {
throw new TransactionException(e);
}
}
return get(groupId);
}
public void initGroup(String groupId) {
redisTemplate.opsForHash().put(REDIS_GROUP_PREFIX + groupId, "root", "");
redisTemplate.expire(REDIS_GROUP_PREFIX + groupId, managerConfig.getDtxTime() + 10000, TimeUnit.MILLISECONDS);
}
服務端會以hash結構在redis創建一個key。hashkey爲“tm:group:”+groupid,key爲root,value爲空。超時時間爲分佈式事務超時時間8s加上10s一共18秒。
2)參與方
設置DTXLocalContext的proxy爲true。
public void preBusinessCode(TxTransactionInfo info) {
// lcn type need connection proxy
DTXLocalContext.makeProxy();
}
2、執行業務方法doBusinessCode
對於執行業務方法,就是執行我們的方法的內容。主要是調用其他服務,而其他服務又會有@LcnTransaction註解,又會走同樣的流程,只是處理類不一樣了,這裏都會寫出不同處理類的處理方式
3、業務執行成功方法onBusinessCodeSuccess
1)發起方
設置系統分佈式事務狀態爲1成功
public void onBusinessCodeSuccess(TxTransactionInfo info, Object result) {
DTXLocalContext.cur().setSysTransactionState(1);
}
2)參與方
加入事務組
public void onBusinessCodeSuccess(TxTransactionInfo info, Object result) throws TransactionException {
log.debug("join group: [GroupId: {},Method: {}]" , info.getGroupId(),
info.getTransactionInfo().getMethodStr());
// join DTX group
transactionControlTemplate.joinGroup(info.getGroupId(), info.getUnitId(), info.getTransactionType(),
info.getTransactionInfo());
}
public void joinGroup(String groupId, String unitId, String transactionType, TransactionInfo transactionInfo)
throws TransactionException {
try {
txLogger.txTrace(groupId, unitId, "join group > transaction type: {}", transactionType);
//加入事務組
reliableMessenger.joinGroup(groupId, unitId, transactionType, DTXLocalContext.transactionState(globalContext.dtxState(groupId)));
txLogger.txTrace(groupId, unitId, "join group message over.");
// 異步檢測,用定時任務去查看是否有異常事務
dtxChecking.startDelayCheckingAsync(groupId, unitId, transactionType);
// 緩存參與方切面信息
aspectLogger.trace(groupId, unitId, transactionInfo);
} catch (RpcException e) {
dtxExceptionHandler.handleJoinGroupMessageException(Arrays.asList(groupId, unitId, transactionType), e);
} catch (LcnBusinessException e) {
dtxExceptionHandler.handleJoinGroupBusinessException(Arrays.asList(groupId, unitId, transactionType), e);
}
txLogger.txTrace(groupId, unitId, "join group logic over");
}
public void joinGroup(String groupId, String unitId, String unitType, int transactionState) throws RpcException, LcnBusinessException {
JoinGroupParams joinGroupParams = new JoinGroupParams();
joinGroupParams.setGroupId(groupId);
joinGroupParams.setUnitId(unitId);
joinGroupParams.setUnitType(unitType);
joinGroupParams.setTransactionState(transactionState);
MessageDto messageDto = request(MessageCreator.joinGroup(joinGroupParams));
if (!MessageUtils.statusOk(messageDto)) {
throw new LcnBusinessException(messageDto.loadBean(Throwable.class));
}
}
客戶端會調用服務端去把當前操作加入到事務組中。
服務端收到後會調用JoinGroupExecuteService類的execute進行處理
public Serializable execute(TransactionCmd transactionCmd) throws TxManagerException {
try {
//根據事務組id獲取事務上下文
DTXContext dtxContext = dtxContextRegistry.get(transactionCmd.getGroupId());
//從消息中獲取參數
JoinGroupParams joinGroupParams = transactionCmd.getMsg().loadBean(JoinGroupParams.class);
txLogger.txTrace(transactionCmd.getGroupId(), joinGroupParams.getUnitId(), "unit:{} try join group:{}",
joinGroupParams.getUnitId(), transactionCmd.getGroupId());
//join
transactionManager.join(dtxContext, joinGroupParams.getUnitId(), joinGroupParams.getUnitType(),
rpcClient.getAppName(transactionCmd.getRemoteKey()), joinGroupParams.getTransactionState());
txLogger.txTrace(transactionCmd.getGroupId(), joinGroupParams.getUnitId(), "unit:{} joined.",
joinGroupParams.getUnitId());
} catch (TransactionException e) {
txLogger.error(this.getClass().getSimpleName(), e.getMessage());
throw new TxManagerException(e.getLocalizedMessage());
}
// non response
return null;
}
public void join(DTXContext dtxContext, String unitId, String unitType, String modId, int userState) throws TransactionException {
//手動回滾時設置狀態爲回滾狀態 0,這個狀態只有在用戶自己設置時可用
if (userState == 0) {
dtxContext.resetTransactionState(0);
}
//構造事務單元
TransactionUnit transactionUnit = new TransactionUnit();
transactionUnit.setModId(modId);
transactionUnit.setUnitId(unitId);
transactionUnit.setUnitType(unitType);
dtxContext.join(transactionUnit);
}
public void join(TransactionUnit transactionUnit) throws TransactionException {
try {
fastStorage.saveTransactionUnitToGroup(groupId, transactionUnit);
} catch (FastStorageException e) {
throw new TransactionException("attempts to join the non-existent transaction group. ["
+ transactionUnit.getUnitId() + '@' + transactionUnit.getModId() + ']');
}
}
public void saveTransactionUnitToGroup(String groupId, TransactionUnit transactionUnit) throws FastStorageException {
if (Optional.ofNullable(redisTemplate.hasKey(REDIS_GROUP_PREFIX + groupId)).orElse(false)) {
redisTemplate.opsForHash().put(REDIS_GROUP_PREFIX + groupId, transactionUnit.getUnitId(), transactionUnit);
return;
}
throw new FastStorageException("attempts to the non-existent transaction group " + groupId,
FastStorageException.EX_CODE_NON_GROUP);
}
服務端收到信息後會在redis與creategroup的hashkey相同數據下保存事務單元信息,key爲事務單元id,key爲事務單元信息
4、業務執行成功後finally中的postBusinessCode
1)發起方
關閉事務組
public void postBusinessCode(TxTransactionInfo info) {
// RPC close DTX group
transactionControlTemplate.notifyGroup(
info.getGroupId(), info.getUnitId(), info.getTransactionType(),
DTXLocalContext.transactionState(globalContext.dtxState(info.getGroupId())));
}
public void notifyGroup(String groupId, String unitId, String transactionType, int state) {
try {
txLogger.txTrace(
groupId, unitId, "notify group > transaction type: {}, state: {}.", transactionType, state);
if (globalContext.isDTXTimeout()) {
throw new LcnBusinessException("dtx timeout.");
}
//通知事務組,進行事務提交
state = reliableMessenger.notifyGroup(groupId, state);
//成功後去提交本地事務
transactionCleanTemplate.clean(groupId, unitId, transactionType, state);
} catch (TransactionClearException e) {
txLogger.trace(groupId, unitId, Transactions.TE, "clean transaction fail.");
} catch (RpcException e) {
dtxExceptionHandler.handleNotifyGroupMessageException(Arrays.asList(groupId, state, unitId, transactionType), e);
} catch (LcnBusinessException e) {
// 關閉事務組失敗
dtxExceptionHandler.handleNotifyGroupBusinessException(Arrays.asList(groupId, state, unitId, transactionType), e.getCause());
}
txLogger.txTrace(groupId, unitId, "notify group exception state {}.", state);
}
通知事務組進行提交事務這邊比較繞
public int notifyGroup(String groupId, int transactionState) throws RpcException, LcnBusinessException {
NotifyGroupParams notifyGroupParams = new NotifyGroupParams();
notifyGroupParams.setGroupId(groupId);
notifyGroupParams.setState(transactionState);
MessageDto messageDto = request0(MessageCreator.notifyGroup(notifyGroupParams),
clientConfig.getTmRpcTimeout() * clientConfig.getChainLevel());
// 成功清理髮起方事務
if (!MessageUtils.statusOk(messageDto)) {
throw new LcnBusinessException(messageDto.loadBean(Throwable.class));
}
return messageDto.loadBean(Integer.class);
}
同樣是向服務端發送請求,服務端接收到請求後用NotifyGroupExecuteService類的execute方法進行處理
public Serializable execute(TransactionCmd transactionCmd) throws TxManagerException {
try {
DTXContext dtxContext = dtxContextRegistry.get(transactionCmd.getGroupId());
// 解析參數
NotifyGroupParams notifyGroupParams = transactionCmd.getMsg().loadBean(NotifyGroupParams.class);
int commitState = notifyGroupParams.getState();
// 獲取事務狀態(當手動回滾時會先設置狀態)
int transactionState = transactionManager.transactionStateFromFastStorage(transactionCmd.getGroupId());
if (transactionState == 0) {
commitState = 0;
}
// 系統日誌
txLogger.txTrace(
transactionCmd.getGroupId(), "", "notify group state: {}", notifyGroupParams.getState());
//如果狀態爲1則事務成功,通知參與模塊進行提交
if (commitState == 1) {
transactionManager.commit(dtxContext);
} else if (commitState == 0) {//如果狀態爲0則事務失敗,通知參與模塊進行回滾
transactionManager.rollback(dtxContext);
}
if (transactionState == 0) {
txLogger.txTrace(transactionCmd.getGroupId(), "", "mandatory rollback for user.");
}
return commitState;
} catch (TransactionException e) {
throw new TxManagerException(e);
} finally {
transactionManager.close(transactionCmd.getGroupId());
// 系統日誌
txLogger.txTrace(transactionCmd.getGroupId(), "", "notify group successfully.");
}
}
public void commit(DTXContext dtxContext) throws TransactionException {
notifyTransaction(dtxContext, 1);
}
//根據groupid從redis獲取所有的事務單元,通知其進行提交
private void notifyTransaction(DTXContext dtxContext, int transactionState) throws TransactionException {
List<TransactionUnit> transactionUnits = dtxContext.transactionUnits();
log.debug("group[{}]'s transaction units: {}", dtxContext.getGroupId(), transactionUnits);
for (TransactionUnit transUnit : transactionUnits) {
NotifyUnitParams notifyUnitParams = new NotifyUnitParams();
notifyUnitParams.setGroupId(dtxContext.getGroupId());
notifyUnitParams.setUnitId(transUnit.getUnitId());
notifyUnitParams.setUnitType(transUnit.getUnitType());
notifyUnitParams.setState(transactionState);
txLogger.txTrace(dtxContext.getGroupId(), notifyUnitParams.getUnitId(), "notify {}'s unit: {}",
transUnit.getModId(), transUnit.getUnitId());
try {
List<String> modChannelKeys = rpcClient.remoteKeys(transUnit.getModId());
if (modChannelKeys.isEmpty()) {
// record exception
throw new RpcException("offline mod.");
}
MessageDto respMsg =
rpcClient.request(modChannelKeys.get(0), MessageCreator.notifyUnit(notifyUnitParams));
if (!MessageUtils.statusOk(respMsg)) {
// 提交/回滾失敗的消息處理
List<Object> params = Arrays.asList(notifyUnitParams, transUnit.getModId());
rpcExceptionHandler.handleNotifyUnitBusinessException(params, respMsg.loadBean(Throwable.class));
}
} catch (RpcException e) {
// 提交/回滾通訊失敗
List<Object> params = Arrays.asList(notifyUnitParams, transUnit.getModId());
rpcExceptionHandler.handleNotifyUnitMessageException(params, e);
} finally {
txLogger.txTrace(dtxContext.getGroupId(), notifyUnitParams.getUnitId(), "notify unit over");
}
}
notifyTransaction方法又會調用客戶端,客戶端用DefaultNotifiedUnitService的execute方法進行處理
public Serializable execute(TransactionCmd transactionCmd) throws TxClientException {
try {
NotifyUnitParams notifyUnitParams = transactionCmd.getMsg().loadBean(NotifyUnitParams.class);
// 保證業務線程執行完畢後執行事務清理操作
//這個參數在參與方會一直是空,在finally會釋放
TxContext txContext = globalContext.txContext(transactionCmd.getGroupId());
if (Objects.nonNull(txContext)) {
synchronized (txContext.getLock()) {
txLogger.txTrace(transactionCmd.getGroupId(), notifyUnitParams.getUnitId(),
"clean transaction cmd waiting for business code finish.");
txContext.getLock().wait();
}
}
// 事務清理操作,主要是提交本地事務
transactionCleanTemplate.clean(
notifyUnitParams.getGroupId(),
notifyUnitParams.getUnitId(),
notifyUnitParams.getUnitType(),
notifyUnitParams.getState());
return true;
} catch (TransactionClearException | InterruptedException e) {
throw new TxClientException(e);
}
畫一張圖來表示流程
2)參與方
空操作,什麼也不幹
default void postBusinessCode(TxTransactionInfo info) {
}
整體流程圖如下,比較粗糙,其中清理事務的已經在上圖畫出。
Finally(runTransaction)的作用就是釋放資源,保證在清理事務時不至於阻塞線程。
一些其他點:
關於參與方如何知道已經存在事務的?
一般我們都是一接口的方式去調用服務,基本就是restTemplate如果用到spring Cloud還會用到Fegin。框架在進行接口調用時都會通過把事務消息放在header中,後邊的服務從header中就會得到當前的事務信息。
ClientHttpRequestInterceptor可以對請求進行攔截,並在其被髮送至服務端之前修改請求或是增強相應的信息。
添加事務組信息到請求頭
RestTemplate調用實現攔截
RestTemplateTracingTransmitter實現了ClientHttpRequestInterceptor接口。並把此攔截器加入到restTemplate的攔截器鏈中,當restTemplate去請求別的資源時,會被RestTemplateTracingTransmitter攔截到,在requestheader中加入groupid信息。
public class RestTemplateTracingTransmitter implements ClientHttpRequestInterceptor {
@Autowired
public RestTemplateTracingTransmitter(@Autowired(required = false) List<RestTemplate> restTemplates) {
if (Objects.nonNull(restTemplates)) {
restTemplates.forEach(restTemplate -> {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
//加入攔截器鏈
interceptors.add(interceptors.size(), RestTemplateTracingTransmitter.this);
});
}
}
@Override
@NonNull
public ClientHttpResponse intercept(
@NonNull HttpRequest httpRequest, @NonNull byte[] bytes,
@NonNull ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
Tracings.transmit(httpRequest.getHeaders()::add);
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
}
public static void transmit(TracingSetter tracingSetter) {
//把信息存儲到header中
if (TracingContext.tracing().hasGroup()) {
log.debug("tracing transmit group:{}", TracingContext.tracing().groupId());
tracingSetter.set(TracingConstants.HEADER_KEY_GROUP_ID, TracingContext.tracing().groupId());
tracingSetter.set(TracingConstants.HEADER_KEY_APP_MAP,
Base64Utils.encodeToString(TracingContext.tracing().appMapString().getBytes(StandardCharsets.UTF_8)));
}
}
fegin實現請求攔截
RequestInterceptor可以實現對請求的攔截
FeignTracingTransmitter實現RequestInterceptor接口在請求資源時在header中加入groupid信息。
public class FeignTracingTransmitter implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
Tracings.transmit(requestTemplate::header);
}
}
從請求頭中獲取事務信息
springboot中要實現攔截請求要用HandlerInterceptor,並且需要在WebMvcConfigurer把此攔截器註冊到InterceptorRegistry
public class SpringTracingApplier implements com.codingapi.txlcn.tracing.http.spring.HandlerInterceptor, WebMvcConfigurer {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//從header中獲取事務組信息
Tracings.apply(request::getHeader);
return true;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this);
}
}
public static void apply(TracingGetter tracingGetter) {
String groupId = Optional.ofNullable(tracingGetter.get(TracingConstants.HEADER_KEY_GROUP_ID)).orElse("");
String appList = Optional.ofNullable(tracingGetter.get(TracingConstants.HEADER_KEY_APP_MAP)).orElse("");
//事務組信息加載到本地上下文
TracingContext.init(Maps.newHashMap(TracingConstants.GROUP_ID, groupId, TracingConstants.APP_MAP,
StringUtils.isEmpty(appList) ? appList : new String(Base64Utils.decodeFromString(appList), StandardCharsets.UTF_8)));
if (TracingContext.tracing().hasGroup()) {
log.debug("tracing apply group:{}, app map:{}", groupId, appList);
}
}