什麼是LCN模式
LCN
模式是TX-LCN
分散式事務模式的一種,L-lock
-鎖定事務單元、C-confirm
-確認事務模組狀態、
notify
-通知事務單元
原理
LCN
模式是通過Spring AOP
的方式代理Connection
的方式實現對本地事務的操作,然後在由TxManager統一協調控制事務。
當本地事務提交回滾或者關閉連線時將會執行假操作,該代理的連線將由LCN連線池管理。
模式特點
該模式對程式碼的嵌入性為低。 該模式僅限於本地存在連線物件且可通過連線物件控制事務的模組。 該模式下的事務提交與回滾是由本地事務方控制,對於資料一致性上有較高的保障。 該模式缺陷在於代理的連線需要隨事務發起方一共釋放連線,增加了連線佔用的時間。
原始碼解讀
首先我們來看幾個關鍵的類DataSourceAspect
-資料來源切面類、TransactionAspect
事務切面類、LcnConnectionProxy
lcn
連線代理類、DTXLogicWeaver
分散式事務排程器、DTXServiceExecutor
分散式事務執行器
DataSourceAspect的作用
原始碼
@Aspect
@Component
public class DataSourceAspect implements Ordered {
private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);
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 this.dtxResourceWeaver.getConnection(() -> {
return (Connection)point.proceed();
});
}
public int getOrder() {
return this.txClientConfig.getResourceOrder();
}
}
由該類的原始碼,我們能夠知道,
lcn
模式主要對資料庫的連線進行了攔截代理。獲取到資料庫的連線交由lcn
來進行代理。
TransactionAspect 作用
原始碼
@Aspect
@Component
public class TransactionAspect implements Ordered {
private static final Logger log = LoggerFactory.getLogger(TransactionAspect.class);
private final TxClientConfig txClientConfig;
private final DTXLogicWeaver dtxLogicWeaver;
public TransactionAspect(TxClientConfig txClientConfig, DTXLogicWeaver dtxLogicWeaver) {
this.txClientConfig = txClientConfig;
this.dtxLogicWeaver = dtxLogicWeaver;
}
@Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.LcnTransaction)")
public void lcnTransactionPointcut() {
}
@Around("lcnTransactionPointcut() && !txcTransactionPointcut()&& !tccTransactionPointcut() && !txTransactionPointcut()")
public Object runWithLcnTransaction(ProceedingJoinPoint point) throws Throwable {
DTXInfo dtxInfo = DTXInfo.getFromCache(point);
LcnTransaction lcnTransaction = (LcnTransaction)dtxInfo.getBusinessMethod().getAnnotation(LcnTransaction.class);
dtxInfo.setTransactionType("lcn");
dtxInfo.setTransactionPropagation(lcnTransaction.propagation());
DTXLogicWeaver var10000 = this.dtxLogicWeaver;
point.getClass();
return var10000.runTransaction(dtxInfo, point::proceed);
}
public int getOrder() {
return this.txClientConfig.getDtxAspectOrder();
}
}
由該類的原始碼,我們能夠明白,通過解析
@LcnTransaction
註解進行相應的操作。程式碼會呼叫到DTXLogicWeaver
類
DTXLogicWeaver 作用
public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable {
if (Objects.isNull(DTXLocalContext.cur())) {
DTXLocalContext.getOrNew();
log.debug("<---- TxLcn start ---->");
DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew();
TxContext txContext;
if (this.globalContext.hasTxContext()) {
txContext = this.globalContext.txContext();
dtxLocalContext.setInGroup(true);
log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId());
} else {
txContext = this.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());
boolean var15 = false;
Object var6;
try {
var15 = true;
var6 = this.transactionServiceExecutor.transactionRunning(info);
var15 = false;
} finally {
if (var15) {
if (dtxLocalContext.isDestroy()) {
synchronized(txContext.getLock()) {
txContext.getLock().notifyAll();
}
if (!dtxLocalContext.isInGroup()) {
this.globalContext.destroyTx();
}
DTXLocalContext.makeNeverAppeared();
TracingContext.tracing().destroy();
}
log.debug("<---- TxLcn end ---->");
}
}
if (dtxLocalContext.isDestroy()) {
synchronized(txContext.getLock()) {
txContext.getLock().notifyAll();
}
if (!dtxLocalContext.isInGroup()) {
this.globalContext.destroyTx();
}
DTXLocalContext.makeNeverAppeared();
TracingContext.tracing().destroy();
}
log.debug("<---- TxLcn end ---->");
return var6;
} else {
return business.call();
}
}
以上程式碼是該類的核心邏輯,可以看出來
TX-LCN
事務的處理全部都是走的這個類的該方法,最終會呼叫到DTXServiceExecutor
分散式事務執行器
DTXServiceExecutor 作用
/**
* 事務業務執行
*
* @param info info
* @return Object
* @throws Throwable Throwable
*/
public Object transactionRunning(TxTransactionInfo info) throws Throwable {
// 1. 獲取事務型別
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);
}
}
通過以上程式碼可以看出,該類是整個事務執行關鍵類。
以上就是LCN模式比較核心的程式碼,其他的分支程式碼就不一一贅述了
實戰
由上一篇分散式事務之TX-LCN 我們規劃了倆個TC
分別是lcn-order
服務和lcn-pay
服務,我們的思路是訂單服務呼叫支付服務,分別在訂單服務表t_order
和支付服務表t_pay
中插入插入資料。
訂單服務核心程式碼和資料表指令碼
程式碼
/**
* @author:triumphxx
* @Date:2021/10/24
* @Time:2:13 下午
* @微信公眾號:北漂碼農有話說
* @網站:http://blog.triumphxx.com.cn
* @GitHub https://github.com/triumphxx
* @Desc:
**/
@RestController
public class LcnOrderController {
@Autowired
TOrderDao tOrderDao;
@Autowired
private RestTemplate restTemplate;
@PostMapping("/add-order")
@Transactional(rollbackFor = Exception.class)
@LcnTransaction
public String add(){
TOrder bean = new TOrder();
bean.setTId(1);
bean.setTName("order");
restTemplate.postForEntity("http://lcn-pay/add-pay","",String.class);
// int i = 1/0;
tOrderDao.insert(bean);
return "新增訂單成功";
}
}
指令碼
CREATE TABLE `t_order` (
`t_id` int(11) NOT NULL,
`t_name` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
支付服務核心程式碼和資料表指令碼
程式碼
/**
* @author:triumphxx
* @Date:2021/10/24
* @Time:2:26 下午
* @微信公眾號:北漂碼農有話說
* @網站:http://blog.triumphxx.com.cn
* @GitHub https://github.com/triumphxx
* @Desc:
**/
@RestController
public class LcnPayController {
@Autowired
TPayDao tPayDao;
@PostMapping("/add-pay")
@Transactional(rollbackFor = Exception.class)
@LcnTransaction
public String addPay(){
TPay tPay = new TPay();
tPay.setTId(1);
tPay.setTName("t_pay");
int i = tPayDao.insertSelective(tPay);
return "新增支付成功";
}
}
指令碼
CREATE TABLE `t_pay` (
`t_id` int(11) NOT NULL,
`t_name` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
測試流程
啟動 Redis
啟動 TM
啟動註冊中心 eureka-server
啟動服務 lcn-order
啟動服務 lcn-pay
請求介面 http://localhost:8001/add-order
程式碼創造異常看資料是否進行回滾
小結
本篇我們分析了TX-LCN
分散式事務的lcn
模式的原理及相關原始碼,以及搭建服務的進行測試。希望能對大家有所幫助。
原始碼地址原始碼傳送門