一.参考
https://github.com/apache/incubator-seata
https://github.com/apache/incubator-seata-samples
二.架构
(一).架构
官网架构图如下.
核心原理:
(二).单测
测试用例依次按顺序启动账号,库存,订单,业务测试服务类.代码入口如下
org.apache.seata.account.DubboAccountServiceStarter#main
org.apache.seata.storage.DubboStorageServiceStarter#main
org.apache.seata.order.DubboOrderServiceStarter#main
org.apache.seata.business.DubboBusinessServiceTester#main.
三.客户端启动源码
启动org.apache.seata.business.DubboBusinessServiceTester#main后,调用堆栈如下图所示:
GlobalTransactionScanner类实现了InitializingBean接口.添加了seata的系统启动时执行GlobalTransactionScanner#afterPropertiesSet方法,进入GlobalTransactionScanner#initClient方法.
该方法依次初始化TM,RM.然后进入GlobalTransactionScanner#findBusinessBeanNamesNeededEnhancement创建带有@GlobalTransactional注解的seata的代理对象.
(一).TM初始化.
进入TMClient#init().单例创建TmNettyRemotingClient对象,进入TmNettyRemotingClient#init方法初始化TM.依次调用registerProcessor,父类init,initConnection()三个方法.
1.TmNettyRemotingClient#registerProcessor注册和TC之间通信消息的处理器.如下图,具体不同消息的处理后面分析.
2.AbstractNettyRemotingClient#init方法向TC发起连接请求,创建线程池轮询发送消息队列.对外要发送的消息存储在AbstractNettyRemotingClient#basketMap队列中.启动netty.
3.initConnection重新初始化连接.
(二).RM初始化.
进入RMClient#init.设置RM对应的DefaultResourceManager,DefaultRMHandler.进入RmNettyRemotingClient#init,逻辑和TmNettyRemotingClient类似.这里只看注册处理器的不同.进入RmNettyRemotingClient#registerProcessor注册处理器,如下图
(三).GlobalTransactionScanner#findBusinessBeanNamesNeededEnhancement
把带有GlobalTransactional注解的bean放入GlobalTransactionScanner#NEED_ENHANCE_BEAN_NAME_SET集合中.
(四).GlobalTransactionScanner#wrapIfNecessary
因为GlobalTransactionScanner类继承了AbstractAutoProxyCreator类.在AbstractAutoProxyCreator#postProcessAfterInitialization方法中回调GlobalTransactionScanner#wrapIfNecessary方法.进入DefaultInterfaceParser#parserInterfaceToProxy,依次处理GlobalTransactional和TwoPhaseBusinessAction的注解.然后把所有带有注解的代理类按照order字段排序后,加到invocationHandlerList的职责链中.在wrapIfNecessary方法中把,职责链的第一个节点赋值到GlobalTransactionScanner#interceptor对象中,然后调用AbstractAutoProxyCreator#wrapIfNecessary创建代理对象.代码如下图:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// do checkers
if (!doCheckers(bean, beanName)) {
return bean;
}
try {
synchronized (PROXYED_SET) {
if (PROXYED_SET.contains(beanName)) {
return bean;
}
if (!NEED_ENHANCE_BEAN_NAME_SET.contains(beanName)) {
return bean;
}
interceptor = null;
//取出所有带有GlobalTransactional和TwoPhaseBusinessAction注解的业务类
ProxyInvocationHandler proxyInvocationHandler = DefaultInterfaceParser.get().parserInterfaceToProxy(bean, beanName);
if (proxyInvocationHandler == null) {
return bean;
}
interceptor = new AdapterSpringSeataInterceptor(proxyInvocationHandler);
LOGGER.info("Bean [{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.toString());
if (!AopUtils.isAopProxy(bean)) {
//创建业务类的代理对象
bean = super.wrapIfNecessary(bean, beanName, cacheKey);
} else {
AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
int pos;
for (Advisor avr : advisor) {
// Find the position based on the advisor's order, and add to advisors by pos
pos = findAddSeataAdvisorPosition(advised, avr);
advised.addAdvisor(pos, avr);
}
}
PROXYED_SET.add(beanName);
return bean;
}
} catch (Exception exx) {
throw new RuntimeException(exx);
}
}
如上图,需要代理的方法放在GlobalTransactionalInterceptorParser#methodsToProxy集合中.如下代码,创建代理对象.
@Override
public ProxyInvocationHandler parserInterfaceToProxy(Object target, String objectName) throws Exception {
Class<?> serviceInterface = DefaultTargetClassParser.get().findTargetClass(target);
Class<?>[] interfacesIfJdk = DefaultTargetClassParser.get().findInterfaces(target);
//检查是否有GlobalTransactional注解
if (existsAnnotation(serviceInterface) || existsAnnotation(interfacesIfJdk)) {
//创建代理对象
ProxyInvocationHandler proxyInvocationHandler = createProxyInvocationHandler();
ConfigurationFactory.getInstance().addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, (CachedConfigurationChangeListener) proxyInvocationHandler);
return proxyInvocationHandler;
}
return null;
}
protected ProxyInvocationHandler createProxyInvocationHandler() {
return new GlobalTransactionalInterceptorHandler(FailureHandlerHolder.getFailureHandler(), methodsToProxy);
}
四.事务执行源码
(一).客户端执行
测试代码如下:
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(DubboBusinessServiceTester.class);
BusinessService businessService = annotationConfigApplicationContext.getBean(BusinessService.class);
Thread thread = new Thread(() -> {
businessService.purchase("U100001", "C00321", 2);});
String outPutRes = "{\"res\": \"success\"}";
try {
businessService.purchase("U100001", "C00321", 2);
if (isInE2ETest()) {
writeE2EResFile(outPutRes);
}
} catch (Exception e) {
if ("random exception mock!".equals(e.getMessage()) && isInE2ETest()) {
writeE2EResFile(outPutRes);
}
throw new RuntimeException(e);
}
thread.start();
//keep run
Thread.currentThread().join();
}
调用带有GlobalTransactional注解的方法此时,进入GlobalTransactionalInterceptorHandler#handleGlobalTransaction方法处理事务.代码如下,调用TransactionalTemplate#execute,这个事务处理的核心方法,用到的模板回调方法在handleGlobalTransaction中声明.
Object handleGlobalTransaction(final InvocationWrapper methodInvocation,
final AspectTransactional aspectTransactional) throws Throwable {
boolean succeed = true;
try {
//这个方法在下面
return transactionalTemplate.execute(new TransactionalExecutor() {
@Override
public Object execute() throws Throwable {
return methodInvocation.proceed();
}
public String name() {
String name = aspectTransactional.getName();
if (!StringUtils.isNullOrEmpty(name)) {
return name;
}
return formatMethod(methodInvocation.getMethod());
}
//创建事务对象
@Override
public TransactionInfo getTransactionInfo() {
// reset the value of timeout
int timeout = aspectTransactional.getTimeoutMills();
if (timeout <= 0 || timeout == DEFAULT_GLOBAL_TRANSACTION_TIMEOUT) {
timeout = defaultGlobalTransactionTimeout;
}
TransactionInfo transactionInfo = new TransactionInfo();
transactionInfo.setTimeOut(timeout);
transactionInfo.setName(name());
transactionInfo.setPropagation(aspectTransactional.getPropagation());
transactionInfo.setLockRetryInterval(aspectTransactional.getLockRetryInterval());
transactionInfo.setLockRetryTimes(aspectTransactional.getLockRetryTimes());
transactionInfo.setLockStrategyMode(aspectTransactional.getLockStrategyMode());
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
for (Class<?> rbRule : aspectTransactional.getRollbackFor()) {
rollbackRules.add(new RollbackRule(rbRule));
}
for (String rbRule : aspectTransactional.getRollbackForClassName()) {
rollbackRules.add(new RollbackRule(rbRule));
}
for (Class<?> rbRule : aspectTransactional.getNoRollbackFor()) {
rollbackRules.add(new NoRollbackRule(rbRule));
}
for (String rbRule : aspectTransactional.getNoRollbackForClassName()) {
rollbackRules.add(new NoRollbackRule(rbRule));
}
transactionInfo.setRollbackRules(rollbackRules);
return transactionInfo;
}
});
} catch (TransactionalExecutor.ExecutionException e) {
GlobalTransaction globalTransaction = e.getTransaction();
// If Participant, just throw the exception to original.
if (globalTransaction.getGlobalTransactionRole() == Participant) {
throw e.getOriginalException();
}
TransactionalExecutor.Code code = e.getCode();
Throwable cause = e.getCause();
boolean timeout = isTimeoutException(cause);
switch (code) {
case RollbackDone:
if (timeout) {
throw cause;
} else {
throw e.getOriginalException();
}
case BeginFailure:
succeed = false;
failureHandler.onBeginFailure(globalTransaction, cause);
throw cause;
case CommitFailure:
succeed = false;
failureHandler.onCommitFailure(globalTransaction, cause);
throw cause;
case RollbackFailure:
failureHandler.onRollbackFailure(globalTransaction, e.getOriginalException());
throw e.getOriginalException();
case Rollbacking:
failureHandler.onRollbacking(globalTransaction, e.getOriginalException());
if (timeout) {
throw cause;
} else {
throw e.getOriginalException();
}
default:
throw new ShouldNeverHappenException(String.format("Unknown TransactionalExecutor.Code: %s", code), e.getOriginalException());
}
} finally {
if (ATOMIC_DEGRADE_CHECK.get()) {
EVENT_BUS.post(new DegradeCheckEvent(succeed));
}
}
}
public Object execute(TransactionalExecutor business) throws Throwable {
// 1. Get transactionInfo
TransactionInfo txInfo = business.getTransactionInfo();
if (txInfo == null) {
throw new ShouldNeverHappenException("transactionInfo does not exist");
}
// 1.1 Get current transaction, if not null, the tx role is 'GlobalTransactionRole.Participant'.
GlobalTransaction tx = GlobalTransactionContext.getCurrent();
// 1.2 Handle the transaction propagation.
Propagation propagation = txInfo.getPropagation();
SuspendedResourcesHolder suspendedResourcesHolder = null;
try {
switch (propagation) {
case NOT_SUPPORTED:
// If transaction is existing, suspend it.
if (existingTransaction(tx)) {
suspendedResourcesHolder = tx.suspend(false);
}
// Execute without transaction and return.
return business.execute();
case REQUIRES_NEW:
// If transaction is existing, suspend it, and then begin new transaction.
if (existingTransaction(tx)) {
suspendedResourcesHolder = tx.suspend(false);
}
tx = GlobalTransactionContext.createNew();
// Continue and execute with new transaction
break;
case SUPPORTS:
// If transaction is not existing, execute without transaction.
if (notExistingTransaction(tx)) {
return business.execute();
}
// Continue and execute with new transaction
break;
case REQUIRED:
// If current transaction is existing, execute with current transaction,else create
tx = GlobalTransactionContext.getCurrentOrCreate();
break;
case NEVER:
// If transaction is existing, throw exception.
if (existingTransaction(tx)) {
throw new TransactionException(
String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s"
, tx.getXid()));
} else {
// Execute without transaction and return.
return business.execute();
}
case MANDATORY:
// If transaction is not existing, throw exception.
if (notExistingTransaction(tx)) {
throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");
}
// Continue and execute with current transaction.
break;
default:
throw new TransactionException("Not Supported Propagation:" + propagation);
}
// set current tx config to holder
GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);
if (tx.getGlobalTransactionRole() == GlobalTransactionRole.Participant) {
LOGGER.info("join into a existing global transaction,xid={}", tx.getXid());
}
try {
// 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,
// else do nothing. Of course, the hooks will still be triggered.
//开始事务
beginTransaction(txInfo, tx);
Object rs;
try {
//执行业务方法
// Do Your Business
rs = business.execute();
} catch (Throwable ex) {
// 3. The needed business exception to rollback.
//回滚事务
completeTransactionAfterThrowing(txInfo, tx, ex);
throw ex;
}
// 4. everything is fine, commit.
//提交事务
commitTransaction(tx, txInfo);
return rs;
} finally {
//5. clear
resumeGlobalLockConfig(previousConfig);
triggerAfterCompletion(tx);
cleanUp(tx);
}
} finally {
// If the transaction is suspended, resume it.
if (suspendedResourcesHolder != null) {
tx.resume(suspendedResourcesHolder);
}
}
}
(二).服务端执行
服务端接收请求的入口为DefaultCoordinator#onRequest.
五.开始事务
(一).客户端开始
进入org.apache.seata.tm.api.TransactionalTemplate#beginTransaction方法,进入DefaultGlobalTransaction#begin(),调用,然后在RootContext上绑定xid.RootContext的RootContext#CONTEXT_HOLDER成员中记录xid.如图,是向TC发起一个开始事务的请求.
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
GlobalBeginRequest request = new GlobalBeginRequest();
request.setTransactionName(name);
request.setTimeout(timeout);
GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
}
return response.getXid();
}
(二).TC服务端开始
如下图,创建GlobalSession.
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
//创建GlobalSession,生产xid
GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name, timeout);
MDC.put(RootContext.MDC_KEY_XID, session.getXid());
session.begin();
// transaction start event
MetricsPublisher.postSessionDoingEvent(session, false);
return session.getXid();
}
六.执行事务
(一).客户端执行
如下图.先保存dml的sql执行前的镜像,然后自动提交执行业务的update的sql,在afterImage中执行修改后的记录的字段的查询sql.在prepareUndoLog中根据before和after的image构造undoLog记录插入UndoLog表.
1.保存sql镜像.进入UpdateExecutor#beforeImage方法,如图就是拼出要修改记录的select的sql,备份到UndoLog表.
2.写入UndoLog表,代码如下.
protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException {
if (beforeImage.getRows().isEmpty() && afterImage.getRows().isEmpty()) {
return;
}
if (SQLType.UPDATE == sqlRecognizer.getSQLType()) {
if (beforeImage.getRows().size() != afterImage.getRows().size()) {
throw new ShouldNeverHappenException("Before image size is not equaled to after image size, probably because you updated the primary keys.");
}
}
ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
TableRecords lockKeyRecords = sqlRecognizer.getSQLType() == SQLType.DELETE ? beforeImage : afterImage;
String lockKeys = buildLockKey(lockKeyRecords);
if (null != lockKeys) {
connectionProxy.appendLockKey(lockKeys);
//设置undoLog表的各个字段
SQLUndoLog sqlUndoLog = buildUndoItem(beforeImage, afterImage);
connectionProxy.appendUndoLog(sqlUndoLog);
}
}
七.提交事务
(一).客户端发起提交
代码如下图,主要是在DefaultGlobalTransaction#commit中向TC发起提交请求.
private void commitTransaction(GlobalTransaction tx, TransactionInfo txInfo)
throws TransactionalExecutor.ExecutionException, TransactionException {
if (tx.getGlobalTransactionRole() != GlobalTransactionRole.Launcher) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Ignore commit: just involved in global transaction [{}]", tx.getXid());
}
return;
}
if (isTimeout(tx.getCreateTime(), txInfo)) {
// business execution timeout
Exception exx = new TmTransactionException(TransactionExceptionCode.TransactionTimeout,
String.format("client detected transaction timeout before commit, so change to rollback, xid = %s", tx.getXid()));
rollbackTransaction(tx, exx);
return;
}
try {
triggerBeforeCommit();
//向TC发起提交请求
tx.commit();
GlobalStatus afterCommitStatus = tx.getLocalStatus();
TransactionalExecutor.Code code = TransactionalExecutor.Code.Unknown;
switch (afterCommitStatus) {
case TimeoutRollbacking:
code = TransactionalExecutor.Code.Rollbacking;
break;
case TimeoutRollbacked:
code = TransactionalExecutor.Code.RollbackDone;
break;
case Finished:
code = TransactionalExecutor.Code.CommitFailure;
break;
default:
}
Exception statusException = null;
if (GlobalStatus.isTwoPhaseHeuristic(afterCommitStatus)) {
statusException = new TmTransactionException(TransactionExceptionCode.CommitHeuristic,
String.format("Global transaction[%s] not found, may be rollbacked.", tx.getXid()));
} else if (GlobalStatus.isOnePhaseTimeout(afterCommitStatus)) {
statusException = new TmTransactionException(TransactionExceptionCode.TransactionTimeout,
String.format("Global transaction[%s] is timeout and will be rollback[TC].", tx.getXid()));
}
if (null != statusException) {
throw new TransactionalExecutor.ExecutionException(tx, statusException, code);
}
triggerAfterCommit();
} catch (TransactionException txe) {
// 4.1 Failed to commit
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.CommitFailure);
}
}
(二).服务端提交
进入.DefaultCore#commit.如下图.
public GlobalStatus commit(String xid) throws TransactionException {
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
if (globalSession == null) {
return GlobalStatus.Finished;
}
if (globalSession.isTimeout()) {
LOGGER.info("TC detected timeout, xid = {}", globalSession.getXid());
return GlobalStatus.TimeoutRollbacking;
}
// just lock changeStatus
//判断是否可以提交
boolean shouldCommit = SessionHolder.lockAndExecute(globalSession, () -> {
boolean shouldCommitNow = false;
if (globalSession.getStatus() == GlobalStatus.Begin) {
// Highlight: Firstly, close the session, then no more branch can be registered.
globalSession.close();
//遍历所有的BranchSession分支事务,如果都可以提交,则全局事务可以提交.
if (globalSession.canBeCommittedAsync()) {
globalSession.asyncCommit();
MetricsPublisher.postSessionDoneEvent(globalSession, GlobalStatus.Committed, false, false);
} else {
globalSession.changeGlobalStatus(GlobalStatus.Committing);
shouldCommitNow = true;
}
//clean session after changing status successfully.
globalSession.clean();
}
return shouldCommitNow;
});
if (shouldCommit) {
//代码下面分析,提交全局事务
boolean success = doGlobalCommit(globalSession, false);
//If successful and all remaining branches can be committed asynchronously, do async commit.
if (success && globalSession.hasBranch() && globalSession.canBeCommittedAsync()) {
globalSession.asyncCommit();
return GlobalStatus.Committed;
} else {
return globalSession.getStatus();
}
} else {
return globalSession.getStatus() == GlobalStatus.AsyncCommitting ? GlobalStatus.Committed : globalSession.getStatus();
}
}
如果应该提交的话,进入DefaultCore#doGlobalCommit方法提交全局事务.
public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException {
boolean success = true;
// start committing event
MetricsPublisher.postSessionDoingEvent(globalSession, retrying);
if (globalSession.isSaga()) {
success = getCore(BranchType.SAGA).doGlobalCommit(globalSession, retrying);
} else {
//遍历所有的分支事务
List<BranchSession> branchSessions = globalSession.getSortedBranches();
Boolean result = SessionHelper.forEach(branchSessions, branchSession -> {
// if not retrying, skip the canBeCommittedAsync branches
if (!retrying && branchSession.canBeCommittedAsync()) {
return CONTINUE;
}
//
BranchStatus currentStatus = branchSession.getStatus();
if (currentStatus == BranchStatus.PhaseOne_Failed) {
SessionHelper.removeBranch(globalSession, branchSession, !retrying);
return CONTINUE;
}
// Only databases with read-only optimization, such as Oracle,
// will report the RDONLY status during XA transactions.
// At this point, the branch transaction can be ignored.
if (currentStatus == BranchStatus.PhaseOne_RDONLY
&& branchSession.getBranchType() == BranchType.XA) {
SessionHelper.removeBranch(globalSession, branchSession, !retrying);
return CONTINUE;
}
try {
//向每个分支事务发起BranchCommitRequest分支事务提交RPC请求.
BranchStatus branchStatus = getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession);
if (isXaerNotaTimeout(globalSession,branchStatus)) {
LOGGER.info("Commit branch XAER_NOTA retry timeout, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
branchStatus = BranchStatus.PhaseTwo_Committed;
}
//省略代码
xxxxx
return success;
}
(三).客户端处理分支事务提交
调用DataSourceManager#branchCommit把提交请求加入到队列AsyncWorker#commitQueue里面.在方法AsyncWorker#dealWithGroupedContexts中处理队列里面的提交请求.处理方法如下,主要是删除UndoLog表里面的记录.
private void dealWithGroupedContexts(String resourceId, List<Phase2Context> contexts) {
if (StringUtils.isBlank(resourceId)) {
//ConcurrentHashMap required notNull key
LOGGER.warn("resourceId is empty and will skip.");
return;
}
DataSourceProxy dataSourceProxy = dataSourceManager.get(resourceId);
if (dataSourceProxy == null) {
LOGGER.warn("failed to find resource for {} and requeue", resourceId);
addAllToCommitQueue(contexts);
return;
}
Connection conn = null;
try {
conn = dataSourceProxy.getPlainConnection();
UndoLogManager undoLogManager = UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType());
// split contexts into several lists, with each list contain no more element than limit size
List<List<Phase2Context>> splitByLimit = Lists.partition(contexts, UNDOLOG_DELETE_LIMIT_SIZE);
for (List<Phase2Context> partition : splitByLimit) {
//删除UndoLog表里面的记录.如果事务不是自动提交,则获取本地连接,提交本地事务.
deleteUndoLog(conn, undoLogManager, partition);
}
} catch (SQLException sqlExx) {
addAllToCommitQueue(contexts);
LOGGER.error("failed to get connection for async committing on {} and requeue", resourceId, sqlExx);
} finally {
IOUtil.close(conn);
}
}
八.回滚事务
(一).客户端回滚
进入TransactionalTemplate#rollbackTransaction,主要是调用DefaultTransactionManager#rollback向TC发起回滚请求.
(二).服务端回滚
八.挂起事务
调用DefaultGlobalTransaction#suspend()清除RootContext的绑定,返回挂起的xid.在调用DefaultGlobalTransaction#resume恢复事务时,把上面保存的xid在写入RootContext中.