@[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。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL5MDM5gjN2ITZzEzNwUDOjlTYmlzNzMGO2EmYkZTOiNzN2IjNwUDN58CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
我這裡使用的是0.1.1版本:
運作程式首先需要配置資料庫并且初始化SQL。
1、修改以下三個配置檔案的MySQL位址
test module中的MySQL配置也改一下:
另外由于我本地的MySQL是8.X版本,是以驅動要更新。
我使用的JDK11,
com.alibaba.fescar.tm.dubbo.impl.OrderServiceImpl
有個BUG,運作時會報錯,需要修改一下:
最後執行以下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發起,首先扣減庫存,然後建立訂單。
啟動server項目的
com.alibaba.fescar.server.Server
的main方法;
啟動example項目的
AccountServiceImpl、OrderServiceImpl、StorageServiceImpl
三個類的main方法,也就是dubbo服務提供者;
以上4個服務啟動完成後,檢視DB中的記錄,會初始化account_tbl和storage_tbl兩張表,插入一條記錄(左側的表)
執行
com.alibaba.fescar.tm.dubbo.impl.BusinessServiceImpl
main方法。會發現執行報錯,DB表資料沒有變更。
是因為在
com.alibaba.fescar.tm.dubbo.impl.BusinessServiceImpl#purchase
方法中存在模拟的異常,我們将其注釋掉,再次執行:
注釋掉以後執行,可以發現沒有報錯,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事務會進行全局事務的送出。
記錄復原日志的關鍵代碼
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版本才可以線上使用。本文隻是簡單了解入門一下,後續在更新幾個版本之後再詳細的分析其源碼。