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 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 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 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
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 restTemplates) {
if (Objects.nonNull(restTemplates)) {
restTemplates.forEach(restTemplate -> {
List 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);
}
}