在動態且複雜的協作器實作中,面對各式各樣的異常情況及不可預期的錯誤是在所難免的。在前幾章節中,我們已經探討了利用 Anser-Saga 的高可用性元件,對被中斷的協作器快照進行補償。
然而,單純的補償協作器並不總是最有效或最合適的解決方案,特別是在某些複雜的業務邏輯或長期執行的交易,這麼輕易地就放棄這些協作器一件很可惜的事情。若在我們清楚了解錯誤為何發生,或是錯誤相對單純的情況下,使協作器能夠重新執行,可能是一個更為理想的選擇。
與上一章一樣,我們對被放入 exit
的協作器進行請求。
再前往 Redis
查看,你應該能看到被備份的協作器快照。
接著將 orderId 給複製出來後,讓我們來確認一下資料庫的資料。
Production Service
history
資料表
依照我們的業務流程,Production Service 的 history
資料表顯示了庫存已扣除成功的相關記錄。
Order Service
order
資料表
再看到 Order Service 的 order
資料表,相關的訂單資訊也被建立成功。我們緊接著來到最後一個微服務:
User Service
history
資料表
因為在最後一個步驟中我們於匿名函式裡埋藏了一個 exit
,所以我們看不到 User Service 有任何扣款記錄的存在。
建立一個 {Anser-Tutorial-Service}/rebuild_restarter.php
檔案,並鍵入以下內容:
<?php
require_once './init.php';
use SDPMlab\Anser\Orchestration\Saga\Restarter\Restarter;
use Orchestrators\CreateOrderOrchestrator;
$restarter = new Restarter();
$result = $restarter->reStartOrchestratorsByServer(
className: CreateOrderOrchestrator::class,
serverName: 'AnserTutorialService',
reBuild: true
);
var_dump($result);
上述的 PHP 程式與上一章所實作的 compensation_restarter.php
大同小異,唯一的不同是我們加入了 bool $reBuild
參數並將他設定為 true
。
在開始執行這個 PHP 程式之前,先記得前往包含錯誤的協作器中,將失敗的罪魁禍首 exit
給註解掉:
//Step4 使用者錢包扣款
$this->setStep()->setCompensationMethod('rollbackUserWalletCharge')
->addAction(
alias: 'walletCharge',
action: static function (CreateOrderOrchestrator $runtimeOrch) {
//exit;
$userKey = $runtimeOrch->getStepAction('userInfo')->getMeaningData()['data']['u_key'];
$total = $runtimeOrch->getStepAction('createOrder')->getMeaningData()['total'];
return $runtimeOrch->userService->walletChargeAction($userKey, $runtimeOrch->orderId, $total);
}
);
最後你就可以直接使用 php rebuild_restarter.php
啟動上述 PHP 程式。順利的話你應該能看到下列內容出現在你的 Command Line 介面:
此時,讓我們去到 User Service 的 history
資料表重新查詢,你應該能看見最後一個步驟被正確完成了!
重新執行成功的 User Service
history
資料表
再去到 Production Service 的 history
資料表看看,你會發現一些有趣的事情:
經歷補償再重新執行的 Production Service
history
資料表
在 Anser Restarter 中,並不存在協作器的「斷點執行」。我們會先透過「補償」行為讓微服務的資料回到最初尚未執行協作器的資料一致狀態,再對協作器進行重試,從步驟 0 重新開始,因此你才會在上述資料表中看見相同的 o_key
經歷了建立、補償、記錄軟性刪除,最後再重新建立的過程。
serverName
地重新執行如果你的協作器使用多個伺服器同時提供服務,你也可以直接透過下列程式碼跳過 serverName
的過濾,對所有符合 className
的協作器進行重新執行:
$restarter = new Restarter();
$result = $restarter->reStartOrchestratorsByClass(
className: CreateOrderOrchestrator::class,
reBuild: true
);
透過 Anser-Saga 的高可用性機制,我們在面對協作器中斷時,擁有了兩個有力的手段來保全資料的一致性和業務流程的完整性,分別是「補償被中斷的協作器快照」和「重新執行被中斷的協作器快照」。
「補償」和「重新執行」各有利弊,選擇哪種方法取決於實際的錯誤情境和業務需求。如果錯誤較為複雜,或是尚未明確找到錯誤原因,那麼「補償」可能是更為安全的選擇;反之,如果錯誤原因明確,且已經得到修復,那麼「重新執行」可能是更為有效和節省資源的選擇。
在實際的情境中,由使用者建立起的訂單或資料若直接進行補償,可能會造成商業行為上的風險,若我們能排除錯誤於補償後自動重試協作器,那麼將獲得更好的使用者體驗;又或者某些錯誤的協作器執行讓使用者被扣除多餘的款項,或者是商品庫存、折價券損失等需要回朔的協作器錯誤,就很適合直接進行補償,使系統回到最初的狀態。
透過合理地選擇補償或重新執行的策略,我們可以更靈活地應對協作器中斷的問題,提高系統的可靠性和健壯性。