天天看點

用PHP輕松完成一個分布式事務TCC,保姆級教程

TCC組成

TCC分為3個階段

Try 階段:嘗試執行,完成所有業務檢查(一緻性), 預留必須業務資源(準隔離性)

Confirm 階段:如果所有分支的Try都成功了,則走到Confirm階段。Confirm真正執行業務,不作任何業務檢查,隻使用 Try 階段預留的業務資源

Cancel 階段:如果所有分支的Try有一個失敗了,則走到Cancel階段。Cancel釋放 Try 階段預留的業務資源。

TCC分布式事務裡,有3個角色,與經典的XA分布式事務一樣:

AP/應用程式,發起全局事務,定義全局事務包含哪些事務分支

RM/資料總管,負責分支事務各項資源的管理

TM/事務管理器,負責協調全局事務的正确執行,包括Confirm,Cancel的執行,并處理網絡異常

如果我們要進行一個類似于銀行跨行轉賬的業務,轉出(TransOut)和轉入(TransIn)分别在不同的微服務裡。

TCC實踐

下面我們進行一個TCC事務的具體開發

目前可用于TCC的開源架構,主要為Java語言,其中以seata為代表。我們的例子采用nodejs,使用的分布式事務架構為dtm,它對分布式事務的支援非常優雅。下面來詳細講解TCC的組成

下面我們來編寫具體的Try/Confirm/Cancel的處理函數

$vega->handleFunc('/api/TransOutTry', function (Mix\Vega\Context $ctx) {
    var_dump('TransOutTry', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');
$vega->handleFunc('/api/TransOutConfirm', function (Mix\Vega\Context $ctx) {
    var_dump('TransOutConfirm', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');
$vega->handleFunc('/api/TransOutCancel', function (Mix\Vega\Context $ctx) {
    var_dump('TransOutCancel', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');

$vega->handleFunc('/api/TransInTry', function (Mix\Vega\Context $ctx) {
    var_dump('TransInTry', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');
$vega->handleFunc('/api/TransInConfirm', function (Mix\Vega\Context $ctx) {
    var_dump('TransInConfirm', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');
$vega->handleFunc('/api/TransInCancel', function (Mix\Vega\Context $ctx) {
    var_dump('TransInCancel', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'SUCCESS']);
})->methods('POST');           

到此各個子事務的處理函數已經OK了,然後是開啟TCC事務,進行分支調用

Dtmcli\tccGlobalTransaction($dtm, function ($tcc) use ($svc) {
        /** @var Dtmcli\Tcc $tcc */
        $req = ['amount' => 30];
        $tcc->callBranch($req, $svc . '/TransOutTry', $svc . '/TransOutConfirm', $svc . '/TransOutCancel');
        $tcc->callBranch($req, $svc . '/TransInTry', $svc . '/TransInConfirm', $svc . '/TransInCancel');
    });
           

至此,一個完整的TCC分布式事務編寫完成。

如果您想要完整運作一個成功的示例,那麼參考這個例子yedf/dtmcli-php-sample,将它運作起來非常簡單

# 部署啟動dtm
# 需要docker版本18以上
git clone https://github.com/yedf/dtm
cd dtm
docker-compose up

# 另起一個指令行
https://github.com/yedf/dtmcli-php-sample.git
cd dtmcli-php-sample
composer install
php demo.php start           

TCC的復原

假如銀行将金額準備轉入使用者2時,發現使用者2的賬戶異常,傳回失敗,會怎麼樣?我們可以讓TransIn傳回失敗來模拟這種情況

$vega->handleFunc('/api/TransInTry', function (Mix\Vega\Context $ctx) {
    var_dump('TransInTry', $ctx->request->getQueryParams(), $ctx->request->getParsedBody());
    $ctx->JSON(200, ['result' => 'FAILURE']);
})->methods('POST');           

這個跟成功的TCC差别就在于,當某個子事務傳回失敗後,後續就復原全局事務,調用各個子事務的Cancel操作,保證全局事務全部復原。

在TCC事務模式上,有不少的讀者會問,如果Confirm/Cancel失敗會怎麼樣?這是一個好問題,代表您正在深入思考TCC事務模式。

第一種情況是臨時失敗,例如網絡故障、應用或資料庫當機,這類錯誤進行重試,最後會傳回成功;另一種情況為業務失敗,按照TCC的協定,第一階段鎖定資源,保證足夠的資源能夠讓Confirm/Cancel執行。

也就是說,程式邏輯上,Confirm/Cancel是不允許傳回業務失敗的,如果出現業務失敗,那麼是bug,需要開發人員手動修複bug。

如果你想開發小程式或者了解更多小程式的内容,可以通過專業開發公司,來實作開發需求:

廈門在乎科技

-專注

廈門小程式開發公司

、APP開發、網站開發、H5小遊戲開發