官 网:http://www.txlcn.org/zh-cn/index.html
git 地址:https://github.com/codingapi/tx-lcn
目录
一、产生背景
由于采用微服务架构,各个模块相互独立,导致原先在Spring容器中的通过@Transactional注解实现的本地事务,无法满足跨服务的分布式事务处理。
二、LCN框架定位
框架本身并不生产事务,LCN只是本地事务的协调工。TX-LCN定位于一款事务协调性框架,框架其本身并不操作事务,而是基于对事务的协调从而达到事务一致性的效果。
三、LCN事务模式
LCN模式是通过代理Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务。当本地事务提交回滚或者关闭连接时将会执行“假”操作,该代理的连接已由LCN框架管理。
模式特点:
- 该模式对代码的嵌入性为低。
- 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。
- 该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。
- 该模式缺陷在于代理的连接需要随事务发起方一起释放连接,增加了连接占用的时间。
四、事务控制原理
TX-LCN由两大模块组成:TxClient、TxManager。TxClient作为模块的依赖框架,提供TX-LCN的标准支持;TxManager作为分布式事务的调控方。事务发起方或者参与方都由TxClient端来控制。
原理图:
五、核心步骤
1、创建事务组
在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标识GroupId的过程。
2、加入事务组
添加事务组是指事务参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。
3、通知事务组
在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块真正的进行提交或回滚事务,并返回结果给事务发起方。
六、数据库连接占用情况
LCN模式:
七、使用
(1)引入依赖 tm tc 对应的各种依赖(可封装,传递引用),配置连接tm 信息 及各种参数配置项信息
(2)需要使用的客户端在其启动类上 增加开启分布式事务注解@EnableDistributedTransaction
(3)需要分布式事务的方法上 增加分布式事务注解@LcnTransaction
八、分布式事务传递性
REQUIRED 当前没有分布式事务,就创建。当前有分布式事务,就加入。
SUPPORTS 当前没有分布式事务,非分布式事务运行。当前有分布式事务,就加入。
九、关键源码分析
LCN事务控制原理是由事务模块TxClient下的代理连接池与TxManager的协调配合完成的事务协调控制。
(1)数据源切面
TxClient的代理连接池实现了javax.sql.DataSource接口,并重写了close方法,事务模块在提交关闭以后TxClient连接池将执行"假关闭"操作,等待TxManager协调完成事务以后再关闭连接。
@Aspect
@Component
@Slf4j
DataSourceAspect{
@Around("execution(* javax.sql.DataSource.getConnection(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
return dtxResourceWeaver.getConnection(() -> (Connection) point.proceed());
}
}
@Component
@Slf4j
public class DTXResourceWeaver {
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();
}
}
(2)各种上下文
TCGlobalContext (全局 分布式存储redis groupId 唯一)
TxContext
DTXLocalContext(本地,当前线程级)
(3)LCN事务拦截器 TransactionAspect
/**
* LCN 事务拦截器
* create by lorne on 2018/1/5
*/
@Aspect
@Component
@Slf4j
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 {
DTXInfo dtxInfo = DTXInfo.getFromCache(point);
LcnTransaction lcnTransaction = dtxInfo.getBusinessMethod().getAnnotation(LcnTransaction.class);
dtxInfo.setTransactionType(Transactions.LCN);
dtxInfo.setTransactionPropagation(lcnTransaction.propagation());
return dtxLogicWeaver.runTransaction(dtxInfo, point::proceed);
}
}
@Component
@Slf4j
public class DTXLogicWeaver {
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("txlcn---有事务上下文,获取父上下文");
log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId());
} else {
// 没有的开启本地事务上下文
log.debug("txlcn---没有事务上下文,开启本地上下文");
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 ---->");
}
}
}
(4)LCN分布式事务业务执行器
/**
* LCN分布式事务业务执行器
* Created by lorne on 2017/6/8.
*/
@Component
@Slf4j
public class DTXServiceExecutor {
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);
}
}
}
(5)本地分布式事务控制器
(6) LcnStartingTransaction#postBusinessCode()
@Service(value = "control_lcn_starting")
@Slf4j
public class LcnStartingTransaction implements DTXLocalControl {
@Override
public void postBusinessCode(TxTransactionInfo info) {
log.debug("txlcn---notify group begin,@group({})", info.getGroupId());
long t1 = System.currentTimeMillis();
// RPC close DTX group
transactionControlTemplate.notifyGroup(
info.getGroupId(), info.getUnitId(), info.getTransactionType(),
DTXLocalContext.transactionState(globalContext.dtxState(info.getGroupId())));
long t2 = System.currentTimeMillis();
long usedTime = t2 - t1;
log.debug("txlcn---postBusinessCode,used time {} ms,@group({})", usedTime, info.getGroupId());
if (usedTime >= 1000) {
log.debug("txlcn---postBusinessCode,more than 1000 ms,used {} ms,@group({})", usedTime, info.getGroupId());
}
}
}
(7)服务端 package com.codingapi.txlcn.tm.txmsg.transaction.NotifyGroupExecuteService
@Service("rpc_notify-group")
@Slf4j
public class NotifyGroupExecuteService implements RpcExecuteService {
@Override
public Serializable execute(TransactionCmd transactionCmd) throws TxManagerException {
long t1 = System.currentTimeMillis();
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;
}
if (commitState == 1) {
transactionManager.commit(dtxContext);
} else if (commitState == 0) {
transactionManager.rollback(dtxContext);
}
return commitState;
} catch (TransactionException e) {
throw new TxManagerException(e);
} finally {
transactionManager.close(transactionCmd.getGroupId());
// 系统日志
txLogger.txTrace(transactionCmd.getGroupId(), "tm", "notify group successfully.");
}
}
}
(8)客户端 package com.codingapi.txlcn.tc.txmsg.transaction.DefaultNotifiedUnitService
/**
* Description: 默认RPC命令业务
* Date: 2018/12/20
*
* @author ujued
*/
@Slf4j
public class DefaultNotifiedUnitService implements RpcExecuteService {
@Override
public Serializable execute(TransactionCmd transactionCmd) throws TxClientException {
long t1 = System.currentTimeMillis();
try {
NotifyUnitParams notifyUnitParams = transactionCmd.getMsg().loadBean(NotifyUnitParams.class);
// 保证业务线程执行完毕后执行事务清理操作
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();
}
}
// 事务清理操作 后面调用connect的commit 或者 rollback
transactionCleanTemplate.clean(
notifyUnitParams.getGroupId(),
notifyUnitParams.getUnitId(),
notifyUnitParams.getUnitType(),
notifyUnitParams.getState());
return true;
} catch (TransactionClearException | InterruptedException e) {
throw new TxClientException(e);
}
}
}
/**
* notify connection
*
* @param state transactionState
* @return RpcResponseState RpcResponseState
*/
public RpcResponseState notify(int state, String groupId, String unitId) {
try {
if (state == 1) {
connection.commit();
} else {
connection.rollback();
}
return RpcResponseState.success;
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
return RpcResponseState.fail;
} finally {
try {
connection.close();
} catch (Exception e1) {
log.error(e1.getLocalizedMessage(), e1);
}
}
}