Seata源码分析之AT模式Rollback

在这里插入图片描述

事务回滚基本原理

TM发送全局事务请求到TC,TC发送branch rollback请求到Order,Account,Storage 三个业务分支,三个业务分支各自回滚本地事务。
上图四个业务代码在seata-samples/spring-boot-mybatis项目
TC在seata项目

Business

1.BusinessController#purchaseCommit 入口
在这里插入图片描述在这里插入图片描述
GlobalTransactional注解拦截,调用下面invoke方法

2.GlobalTransactionalInterceptor#invoke
在这里插入图片描述

3.GlobalTransactionalInterceptor#handleGlobalTransaction
在这里插入图片描述
4.TransactionalTemplate#execute
在这里插入图片描述
中间的business.execute()执行的最终代码是下面红色框的业务代码,代码位置BusinessService#purchase
在这里插入图片描述
在Account那里故意抛出异常,所以上面的business.execute这一步会报错,进而执行completeTransactionAfterThrowing方法。
Account异常代码位置AccountService#debit
在这里插入图片描述
5.TransactionalTemplate#completeTransactionAfterThrowing
在这里插入图片描述
6.TransactionalTemplate#rollbackTransaction
在这里插入图片描述
7.DefaultGlobalTransaction#rollback
在这里插入图片描述
8.DefaultTransactionManager#rollback
在这里插入图片描述
生成全局rollback请求,设置xid,发送同步请求到TC。

下面看看TC服务端是怎么处理的

TC

9.AbstractRpcRemoting#channelRead
在这里插入图片描述
接收Business的Rollback消息

10.AbstractRpcRemotingServer#dispatch
在这里插入图片描述
11.DefaultServerMessageListenerImpl#onTrxMessage
在这里插入图片描述
12.DefaultCoordinator#onRequest
在这里插入图片描述
13.AbstractTCInboundHandler#handle
在这里插入图片描述
14.DefaultCoordinator#doGlobalRollback
在这里插入图片描述
15.DefaultCore#rollback
在这里插入图片描述
16.DefaultCore#doGlobalRollback
在这里插入图片描述
循环遍历Order,Storage,Account分支,给每个业务分支发送branchRollback请求,回滚本地事务。

17.AbstractCore#branchRollback
在这里插入图片描述
下面看看Account分支的处理逻辑,其他两个分支原理类似。

Account

18.RmMessageListener#onMessage
在这里插入图片描述
接收TC的BranchRollback消息

19.RmMessageListener#handleBranchRollback
在这里插入图片描述
20.AbstractRMHandler#handle
在这里插入图片描述
21.AbstractRMHandler#doBranchRollback
在这里插入图片描述
22.AbstractUndoLogManager#undo
下面是回滚的关键代码

public void undo(DataSourceProxy dataSourceProxy, String xid, long branchId) throws TransactionException {
        Connection conn = null;
        ResultSet rs = null;
        PreparedStatement selectPST = null;
        boolean originalAutoCommit = true;

        for (; ; ) {
            try {
                conn = dataSourceProxy.getPlainConnection();

                // The entire undo process should run in a local transaction.
                if (originalAutoCommit = conn.getAutoCommit()) {
                    conn.setAutoCommit(false);
                }

                // Find UNDO LOG
                selectPST = conn.prepareStatement(SELECT_UNDO_LOG_SQL);
                selectPST.setLong(1, branchId);
                selectPST.setString(2, xid);
                rs = selectPST.executeQuery();

                boolean exists = false;
                while (rs.next()) {
                    exists = true;

                    // It is possible that the server repeatedly sends a rollback request to roll back
                    // the same branch transaction to multiple processes,
                    // ensuring that only the undo_log in the normal state is processed.
                    int state = rs.getInt(ClientTableColumnsName.UNDO_LOG_LOG_STATUS);
                    if (!canUndo(state)) {
                        if (LOGGER.isInfoEnabled()) {
                            LOGGER.info("xid {} branch {}, ignore {} undo_log", xid, branchId, state);
                        }
                        return;
                    }

                    String contextString = rs.getString(ClientTableColumnsName.UNDO_LOG_CONTEXT);
                    Map<String, String> context = parseContext(contextString);
                    byte[] rollbackInfo = getRollbackInfo(rs);

                    String serializer = context == null ? null : context.get(UndoLogConstants.SERIALIZER_KEY);
                    UndoLogParser parser = serializer == null ? UndoLogParserFactory.getInstance()
                        : UndoLogParserFactory.getInstance(serializer);
                    BranchUndoLog branchUndoLog = parser.decode(rollbackInfo);

                    try {
                        // put serializer name to local
                        setCurrentSerializer(parser.getName());
                        List<SQLUndoLog> sqlUndoLogs = branchUndoLog.getSqlUndoLogs();
                        if (sqlUndoLogs.size() > 1) {
                            Collections.reverse(sqlUndoLogs);
                        }
                        for (SQLUndoLog sqlUndoLog : sqlUndoLogs) {
                            TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(dataSourceProxy.getDbType()).getTableMeta(
                                conn, sqlUndoLog.getTableName(), dataSourceProxy.getResourceId());
                            sqlUndoLog.setTableMeta(tableMeta);
                            AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(
                                dataSourceProxy.getDbType(), sqlUndoLog);
                            undoExecutor.executeOn(conn);
                        }
                    } finally {
                        // remove serializer name
                        removeCurrentSerializer();
                    }
                }

                // If undo_log exists, it means that the branch transaction has completed the first phase,
                // we can directly roll back and clean the undo_log
                // Otherwise, it indicates that there is an exception in the branch transaction,
                // causing undo_log not to be written to the database.
                // For example, the business processing timeout, the global transaction is the initiator rolls back.
                // To ensure data consistency, we can insert an undo_log with GlobalFinished state
                // to prevent the local transaction of the first phase of other programs from being correctly submitted.
                // See https://github.com/seata/seata/issues/489

                if (exists) {
                    deleteUndoLog(xid, branchId, conn);
                    conn.commit();
                    if (LOGGER.isInfoEnabled()) {
                        LOGGER.info("xid {} branch {}, undo_log deleted with {}", xid, branchId,
                            State.GlobalFinished.name());
                    }
                } else {
                    insertUndoLogWithGlobalFinished(xid, branchId, UndoLogParserFactory.getInstance(), conn);
                    conn.commit();
                    if (LOGGER.isInfoEnabled()) {
                        LOGGER.info("xid {} branch {}, undo_log added with {}", xid, branchId,
                            State.GlobalFinished.name());
                    }
                }

                return;
            } catch (SQLIntegrityConstraintViolationException e) {
                // Possible undo_log has been inserted into the database by other processes, retrying rollback undo_log
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("xid {} branch {}, undo_log inserted, retry rollback", xid, branchId);
                }
            } catch (Throwable e) {
                if (conn != null) {
                    try {
                        conn.rollback();
                    } catch (SQLException rollbackEx) {
                        LOGGER.warn("Failed to close JDBC resource while undo ... ", rollbackEx);
                    }
                }
                throw new BranchTransactionException(BranchRollbackFailed_Retriable, String
                    .format("Branch session rollback failed and try again later xid = %s branchId = %s %s", xid,
                        branchId, e.getMessage()), e);

            } finally {
                try {
                    if (rs != null) {
                        rs.close();
                    }
                    if (selectPST != null) {
                        selectPST.close();
                    }
                    if (conn != null) {
                        if (originalAutoCommit) {
                            conn.setAutoCommit(true);
                        }
                        conn.close();
                    }
                } catch (SQLException closeEx) {
                    LOGGER.warn("Failed to close JDBC resource while undo ... ", closeEx);
                }
            }
        }
    }

查询数据库表undo_log,获取rollback_info字段信息,rollback_info包括提交事务之前跟之后的表变动信息,根据这些信息恢复表的数据。
下图是rollback_info的示例样式,回滚依据beforeImage这部分。
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值