1.当全局事务检测到异常向TC发起回滚,TC收到回滚请求并通知所有的分支事务进行回滚,但是其中有一个分支事务由于网络问题导致TC通知不到,TC如果确保回滚成功?
/**
* 回滚重试,每1s执行一次
*/
protected void handleRetryRollbacking() {
// 查询出状态为TimeoutRollbacking,TimeoutRollbackRetrying,RollbackRetrying,Rollbacking的全局事务
SessionCondition sessionCondition = new SessionCondition(rollbackingStatuses);
sessionCondition.setLazyLoadBranch(true);
Collection<GlobalSession> rollbackingSessions =
SessionHolder.getRetryRollbackingSessionManager().findGlobalSessions(sessionCondition);
if (CollectionUtils.isEmpty(rollbackingSessions)) {
return;
}
// 遍历这些全局事务
long now = System.currentTimeMillis();
SessionHelper.forEach(rollbackingSessions, rollbackingSession -> {
try {
// 尽量防止重复回滚
// 比如TC端收到全局事务的rollback请求会先把全局事务的状态改成Rollbacking,然后再去通知各个分支事务进行回滚
// 在这段时间间隔里面,该定时任务还在跑就会造成重复通知分支事务回滚,所以这里进行一下过滤
if (rollbackingSession.getStatus().equals(GlobalStatus.Rollbacking)
&& !rollbackingSession.isDeadSession()) {
// 跳过
return;
}
// 默认这个if条件都不成立
if (isRetryTimeout(now, MAX_ROLLBACK_RETRY_TIMEOUT.toMillis(), rollbackingSession.getBeginTime())) {
if (ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE) {
// 释放全局事务的全局锁
rollbackingSession.clean();
}
// Prevent thread safety issues
// 把全局事务从持久层进行移除
SessionHolder.getRetryRollbackingSessionManager().removeGlobalSession(rollbackingSession);
LOGGER.error("Global transaction rollback retry timeout and has removed [{}]", rollbackingSession.getXid());
SessionHelper.endRollbackFailed(rollbackingSession, true);
// rollback retry timeout event
MetricsPublisher.postSessionDoneEvent(rollbackingSession, GlobalStatus.RollbackRetryTimeout, true, false);
//The function of this 'return' is 'continue'.
return;
}
// 再次执行doGlobalRollback方法,也就是说再次往RM发起回滚请求
rollbackingSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
core.doGlobalRollback(rollbackingSession, true);
} catch (TransactionException ex) {
LOGGER.info("Failed to retry rollbacking [{}] {} {}", rollbackingSession.getXid(), ex.getCode(), ex.getMessage());
}
});
}
- 如果是因为网络抖动的原因,TC的重试回滚定时器会去找到状态为Rollbacking的全局事务,然后对这个全局事务下面还没有回滚成功的分支事务再次去通知重试回滚
- 如果是因为分支事务的服务已经挂,那么当该服务重启的时候,会去重新向TC进行注册,分支事务中会记录资源id和客户端id,在后台线程重试回滚的时候就可以根据这两个id找到该服务的channel从而进行发送重试回滚的通知了
2.当全局事务中的所有分支事务都执行成功后,TM向TC发起commit请求失败,此时会发生什么?
/**
* 全局事务超时校验,查询状态为Begin的全局事务,然后校验是否已经超时,如果超时了就把状态改为TimeoutRollbacking
*/
protected void timeoutCheck() {
// 从持久层中查询出所有状态为Begin的全局事务
SessionCondition sessionCondition = new SessionCondition(GlobalStatus.Begin);
sessionCondition.setLazyLoadBranch(true);
Collection<GlobalSession> beginGlobalsessions =
SessionHolder.getRootSessionManager().findGlobalSessions(sessionCondition);
if (CollectionUtils.isEmpty(beginGlobalsessions)) {
return;
}
if (!beginGlobalsessions.isEmpty() && LOGGER.isDebugEnabled()) {
LOGGER.debug("Global transaction timeout check begin, size: {}", beginGlobalsessions.size());
}
// 遍历查询出来的所有全局事务
SessionHelper.forEach(beginGlobalsessions, globalSession -> {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
globalSession.getXid() + " " + globalSession.getStatus() + " " + globalSession.getBeginTime() + " "
+ globalSession.getTimeout());
}
SessionHolder.lockAndExecute(globalSession, () -> {
// 如果全局事务还没有超时就跳过
if (globalSession.getStatus() != GlobalStatus.Begin || !globalSession.isTimeout()) {
return false;
}
LOGGER.info("Global transaction[{}] is timeout and will be rollback.", globalSession.getXid());
// 更新全局事务的状态为TimeoutRollbacking,并设置为不活跃的状态
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
globalSession.close();
globalSession.setStatus(GlobalStatus.TimeoutRollbacking);
globalSession.addSessionLifecycleListener(SessionHolder.getRetryRollbackingSessionManager());
SessionHolder.getRetryRollbackingSessionManager().addGlobalSession(globalSession);
// transaction timeout and start rollbacking event
MetricsPublisher.postSessionDoingEvent(globalSession, GlobalStatus.TimeoutRollbacking.name(), false, false);
return true;
});
});
if (!beginGlobalsessions.isEmpty() && LOGGER.isDebugEnabled()) {
LOGGER.debug("Global transaction timeout check end. ");
}
}
TC后台会有一个校验线程去查询出所有Begin状态的全局事务,如果发现全局事务已经超时(当前时间-创建时间 > 超时时间),那么就会把这个全局事务的状态改为TimeoutRollbacking。此时回滚重试定时器就能查询出该全局事务,然后对该全局事务下的所有分支事务进行回滚,如果回滚失败,就把状态改为TimeoutRollbackRetrying,然后继续重试回滚,在完成回滚之前全局锁不会被释放
3.当全局事务中的所有分支事务都执行成功后,TM向TC发起rollback请求失败,此时会发生什么?
与第二点相同
4.当其中一个分支事务的第一阶段失败会发生什么?
分支事务在执行完业务sql以及undoLog的sql之后,会去往TC注册分支事务信息,同时去获取全局锁,然后再提交本地事务,最后提交完事务之后上报提交结果给TC
- 如果RM往TC注册分支事务失败(发送请求给TC失败),此时RM会直接往外抛异常,触发全局事务向TC发起全局事务回滚操作
- 如果RM获取全局锁失败,此时会重试获取全局锁,直到获取全局锁超时之后,RM往外抛异常触发全局事务向TC发起全局事务回滚操作
- RM向TC上报提交结果失败(无论提交成功还是失败),此时会重试上报操作,如果重试之后还是失败,则RM往外抛出异常触发全局事务向TC发起全局事务回滚操作
5.两个线程,线程A在执行完分支事务之后就抛出了异常导致全局事务进行回滚,线程B由于线程A获取了全局锁所以在等待着,而线程A在进行undoLog回滚的时候由于线程B获取到了该行数据的本地锁,所以也需要等待线程B释放本地锁,也就是线程B等待线程A的全局锁,线程A则等待线程B的本地锁,此时造成了死锁的情况,那这种情况下seata是怎么解决的?
通过全局锁的超时机制解决,线程B在等待全局锁超时后会主动释放全局锁,然后进而释放本地锁