天天看點

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

@[toc]

簡介

阿裡巴巴近日開源了分布式事務中間件

fescar

。GitHub位址是

https://github.com/alibaba/fescar

官方中文文檔:

https://github.com/alibaba/fescar/wiki/Home_Chinese

但是現在中文文檔連接配接都不對,打不開,不知為何。

阿裡巴巴現在内部使用的版本是GTS,fescar是其開源社群版本,fescar是

Fast & EaSy Commit And Rollback, FESCAR

的簡稱。更多簡介請參考

==fescar架構簡介參考: 阿裡巴巴開源分布式事務解決方案 Fescar ==

運作官方demo

克隆到本地IDEA,demo運作使用到的是examples和server。

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

我這裡使用的是0.1.1版本:

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

運作程式首先需要配置資料庫并且初始化SQL。

1、修改以下三個配置檔案的MySQL位址

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

test module中的MySQL配置也改一下:

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

另外由于我本地的MySQL是8.X版本,是以驅動要更新。

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

我使用的JDK11,

com.alibaba.fescar.tm.dubbo.impl.OrderServiceImpl

有個BUG,運作時會報錯,需要修改一下:

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

最後執行以下SQL語句初始化表:

DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;           

好了,準備工作做完。

執行個體簡述:

該執行個體模拟一個下單功能,由businessService發起,首先扣減庫存,然後建立訂單。

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

啟動server項目的

com.alibaba.fescar.server.Server

的main方法;

啟動example項目的

AccountServiceImpl、OrderServiceImpl、StorageServiceImpl

三個類的main方法,也就是dubbo服務提供者;

以上4個服務啟動完成後,檢視DB中的記錄,會初始化account_tbl和storage_tbl兩張表,插入一條記錄(左側的表)

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

執行

com.alibaba.fescar.tm.dubbo.impl.BusinessServiceImpl

main方法。會發現執行報錯,DB表資料沒有變更。

是因為在

com.alibaba.fescar.tm.dubbo.impl.BusinessServiceImpl#purchase

方法中存在模拟的異常,我們将其注釋掉,再次執行:

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

注釋掉以後執行,可以發現沒有報錯,DB中的資料已經正确修改(參見上圖的右側三張表的資料)。

至此demo運作成功。

事務復原原理簡介

根據

介紹的原理,簡單看看其rollback的原理。後續專門分析一下fescar的源碼。

講到它是優化了兩階段送出,減少鎖的時間,利用本地事務真正送出事務,并且記錄可用于復原的日志,然後出錯時根據日志復原。

TransactionalTemplate

是核心入口類;

/*
 *  Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.alibaba.fescar.tm.api;

import com.alibaba.fescar.core.exception.TransactionException;

/**
 * Template of executing business logic with a global transaction.
 */
public class TransactionalTemplate {

    /**
     * Execute object.
     *
     * @param business the business
     * @return the object
     * @throws TransactionalExecutor.ExecutionException the execution exception
     */
    public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {

        // 1. get or create a transaction
        //擷取全局事務管理器
        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();

        // 2. begin transaction  事務begin
        try {
            tx.begin(business.timeout(), business.name());

        } catch (TransactionException txe) {
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                TransactionalExecutor.Code.BeginFailure);

        }

        Object rs = null;
        try {

            // Do Your Business  執行我們具體的業務邏輯
            rs = business.execute();

        } catch (Throwable ex) {

            // 3. any business exception, rollback.  出錯時復原
            try {
                tx.rollback();

                // 3.1 Successfully rolled back
                throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);

            } catch (TransactionException txe) {
                // 3.2 Failed to rollback
                throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.RollbackFailure, ex);

            }

        }

        // 4. everything is fine, commit.  事務送出
        try {
            tx.commit();

        } catch (TransactionException txe) {
            // 4.1 Failed to commit
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                TransactionalExecutor.Code.CommitFailure);

        }
        return rs;
    }

}
           

事務送出有兩種:

public enum GlobalTransactionRole {

    /**
     * The Launcher.
     * 開啟全局事務的發起者
     */
    // The one begins the current global transaction.
    Launcher,

    /**
     * The Participant.
     * 分支事務,也就是分布在各個系統中的本地事務
     */
    // The one just joins into a existing global transaction.
    Participant
}           

通過代碼可以看到,分支事務什麼都不做,也就是直接送出本地事務。Launcher事務會進行全局事務的送出。

阿裡開源分布式事務Fescar demo示例及原理分析簡介運作官方demo事務復原原理簡介

記錄復原日志的關鍵代碼

com.alibaba.fescar.rm.datasource.undo.UndoLogManager#flushUndoLogs

中的

undoLogContent

public static void flushUndoLogs(ConnectionProxy cp) throws SQLException {
        assertDbSupport(cp.getDbType());

        ConnectionContext connectionContext = cp.getContext();
        String xid = connectionContext.getXid();
        long branchID = connectionContext.getBranchId();

        BranchUndoLog branchUndoLog = new BranchUndoLog();
        branchUndoLog.setXid(xid);
        branchUndoLog.setBranchId(branchID);
        branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems());

        String undoLogContent = UndoLogParserFactory.getInstance().encode(branchUndoLog);

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Flushing UNDO LOG: " + undoLogContent);
        }

        PreparedStatement pst = null;
        try {
            pst = cp.getTargetConnection().prepareStatement(INSERT_UNDO_LOG_SQL);
            pst.setLong(1, branchID);
            pst.setString(2, xid);
            pst.setBlob(3, BlobUtils.string2blob(undoLogContent));
            pst.executeUpdate();
        } catch (Exception e) {
            if (e instanceof SQLException) {
                throw (SQLException) e;
            } else {
                throw new SQLException(e);
            }
        } finally {
            if (pst != null) {
                pst.close();
            }
        }

    }           

這裡根據上面的執行個體,檢視其中一條日志:

{
    "branchId": 1890459,
    "sqlUndoLogs": [{
        "afterImage": {
            "rows": [{
                "fields": [{
                    "keyType": "PrimaryKey",
                    "name": "ID",
                    "type": 4,
                    "value": 8
                }, {
                    "keyType": "NULL",
                    "name": "MONEY",
                    "type": 4,
                    "value": 199
                }]
            }],
            "tableName": "account_tbl"
        },
        "beforeImage": {
            "rows": [{
                "fields": [{
                    "keyType": "PrimaryKey",
                    "name": "ID",
                    "type": 4,
                    "value": 8
                }, {
                    "keyType": "NULL",
                    "name": "MONEY",
                    "type": 4,
                    "value": 599
                }]
            }],
            "tableName": "account_tbl"
        },
        "sqlType": "UPDATE",
        "tableName": "account_tbl"
    }],
    "xid": "10.240.130.105:8091:1890457"
}           

可以看到,日志中記錄了修改之前和之後的資料變化情況,也就是資料鏡像,復原時就是根據這個進行復原的。

由于現在fescar才剛剛開源,遠沒有達到商用,需要到1.0版本才可以線上使用。本文隻是簡單了解入門一下,後續在更新幾個版本之後再詳細的分析其源碼。