iT邦幫忙

2025 iThome 鐵人賽

DAY 8
0

分散式流程不是線性世界

  • 在單機程式裡,我們習慣「一步步執行」。
  • 在分散式系統裡,A、B、C 三個服務的呼叫雖然在語意上表現為先後順序,但在實際執行層面上,則透過獨立的非同步訊息傳遞進行,不再受單一呼叫鏈的線性控制。

容易犯錯的順序性問題,核心在於:

  1. 誤信基礎設施(網路、MQ、DB)會幫你保序。
  2. 誤把跨服務流程當單機線性執行,忘記可能亂序到達。
  3. 誤把一致性誤認為順序性,誤以為只要達到一致性,就能避免順序錯誤;結果在 replay 或補償時,事件雖然一致卻不在正確的順序,導致狀態錯亂。
常見誤解 真實分散式行為 典型後果
網路/佇列 (Queue) 會保證全域順序(「我先發 A,再發 B,對方一定先看到 A 再看到 B」) 網路封包可能亂序抵達;MQ 只保證 partition 內有序,跨 partition 無序 訊息 B 比 A 先處理,流程語意錯亂
跨服務流程等同單機線性執行(「呼叫 A → B → C,一定照順序跑完」) A、B、C 可能因延遲/重試/並行導致先後顛倒 先寄歡迎信再建帳號、先扣庫存再下單
最終一致性 = 最後一定正確(「就算慢一點,狀態最後會對」) 如果事件 replay/複製順序錯誤,狀態可能「一致但錯誤」 Event Sourcing 重播失敗、投影資料錯誤

那麼有什麼解決辦法呢?

分散式系統中的 Ordering 解法

層次 技術範例 保序範圍 邏輯維護成本 吞吐量影響 延遲影響 基礎設施成本 適用場景建議
流程語意層 (Workflow) Temporal Workflow、Camunda Process Instance 單一流程的事件/步驟執行順序 :邏輯直觀,引擎管理 replay;但需撰寫補償邏輯,遵守 deterministic 限制 單流程:低吞吐;全系統:高吞吐(多流程並行) 步驟累積 + replay → 延遲偏高 :需 Workflow 引擎 跨服務業務流程(下單、補償交易、長流程)
單一責任序列化層 (Actor / Entity Workflow) Temporal Entity Pattern、Akka Actor單執行緒消費者 同一資源的狀態轉換執行順序 :邏輯直觀,框架保序;但需正確設計 sharding key 避免熱點 單 Entity:低吞吐;全系統:高吞吐(可水平擴展) 單點熱點瓶頸導致排隊延遲上升:隊列排隊時間增加 中等:需 actor 系統或 workflow runtime 銀行帳戶交易、商品庫存操作、使用者錢包
事件排序 / 版本控制層 Event Sourcing (event number)、Optimistic Lock、Lamport/Vector Clock 事件順序重放執行(同一資源事件需依序處理) 中等:需維護版本號、處理衝突、理解 replay 與邏輯時鐘 單資源:受衝突率影響;全系統:高吞吐(資源可並行) 固定低延遲:單次版本比對或 log append :僅需現有 DB/Log 訂單狀態更新、帳戶餘額快照、Event replay
資料通道層 (Partition 保序) Kafka Partition、SQS FIFO 同一 Partition 內的訊息傳輸順序 低(單 partition):幾乎零負擔;中(跨 partition):需協調一致性 單 Partition:低吞吐;全系統:高吞吐(靠多 Partition 擴展) 可能因 partition backlog 或 rebalance 增加 中高:需要 MQ 基礎設施 大規模事件流處理、日誌收集、IoT 資料上傳
分散式鎖 (Distributed Lock) ZooKeeper Lock、etcd lease、Consul session 同一臨界區的進入順序 中等:需處理鎖超時、死鎖、重入鎖等邊界情況 全系統:低吞吐(序列化臨界區,無法水平擴展) 每次 acquire/release 都需共識 → 高延遲 :依賴共識協定 infra 組態更新、Leader 選舉、全域唯一 ID 產生
全域共識層 (Consensus) Raft、Paxos、ZooKeeper (ZAB) 全域 Log 的提交順序 :需遵循 log append 模型,並處理 quorum / leader 切換語意 全系統:最低吞吐(全域序列化,無法擴展) 跨節點共識 + 持久化 → 必然高延遲 最高:需完整共識協定 分散式鎖服務、元資料管理

之後會有討論「單一責任序列化層」的篇章,本篇則用註冊流程先討論「流程語意層」的解法。

範例

依照現前的範例,註冊流程通常會拆分成多個服務:

  1. 建立帳號
  2. 送點數
  3. 寄出歡迎信

如果只靠訊息隊列和多個 consumer 平行處理,可能出現順序錯亂或狀態不一致:

  • 信件先寄出,但帳號還沒建立。
  • 點數送到一個不存在的使用者 ID。
  • 某個步驟失敗後重試,導致流程卡在一半,最終結果前後不一致。

範例解法 - 流程語意層的保證

在 Temporal 中,可以將整個「註冊流程」建模為一個 Registration Workflow:

  • Workflow instance:每一次註冊請求,對應一個獨立的 Workflow。
  • 步驟序列:Workflow 內依序執行三個 Activity:
    1. 建立帳號
    2. 送點數給使用者
    3. 寄出歡迎信

回頭看一下程式碼

Start Trigger 重點:

  • 設定唯一的 Workflow ID(以 userId 組成)
  • 保證註冊流程的步驟順序:帳號 → 點數 → 歡迎信
  • 避免因並行處理導致的流程語意錯亂
public class Process1StartTrigger {
  public static void main(String[] args) {
    WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
    WorkflowClient client = WorkflowClient.newInstance(service);

    String userId = UUID.randomUUID().toString();
    String email = "flow@gmail.com";

    RegistrationWorkflow workflow = client.newWorkflowStub(
        RegistrationWorkflow.class,
        WorkflowOptions.newBuilder()
            .setTaskQueue(TASK_QUEUE)
            .setWorkflowId("registration-" + userId) // 4. Workflow 唯一 ID
            .build());

    RegisterRequest request = new RegisterRequest(userId, email);
    WorkflowExecution execution = WorkflowClient.start(workflow::register, request);
    System.out.println("WorkflowId: " + execution.getWorkflowId());
    System.out.println("RunId: " + execution.getRunId());
  }
}

RegistrationWorkflowImpl 重點

流程語意層的保證:

  • Temporal 會把這些步驟完整記錄在 event history。
  • 無論發生延遲、重試或 replay,最終都會嚴格依照定義的流程順序執行。
  • 保證「帳號 → 點數 → 信件」這個流程語意不被破壞。

效果:

  • 帳號一定存在後才會送點數。
  • 點數一定發放後才會寄出歡迎信。
  • 失敗可安全重試,流程不會卡在中間,也不會產生亂序。
public class RegistrationWorkflowImpl implements RegistrationWorkflow {
  private final RegistrationActivities acts;

  @Override
  public void register(RegisterRequest req) {
    // Step 1: 建帳號
    acts.createAccount(req);

    // Step 2: 送 500 點
    acts.addPoints(req.getUserId(), 500, "signupBonus");

    // Step 3: 寄歡迎信
    acts.sendEmail(req.getEmail(), "welcome-" + req.getUserId());
  }
}

結語

分散式系統的順序性挑戰,是架構設計中的核心課題。唯有先理解問題的根源,再依需求選擇合適的解法,才能打造出既可靠又高效的流程系統。

下一篇我們來討論冪等性問題。


上一篇
Day07 - 分散式流程可靠性的基石 - State Persistence & Replay
下一篇
Day09 - 不管是一次、一百次還是一萬次,我還是想跟你說我愛...冪等性
系列文
Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言