文章二:Temporal Worker Architecture and Scaling

前言與目標

段落重點 Temporal worker 極其複雜,軟體工程師需要理解其工作原理、如何調整以及在規模化運行時如何避免常見陷阱。本文的目標是從一個簡單的「黑盒子」Temporal worker 開始,逐步揭示其內部元件的完整架構,更重要的是,解釋軟體/DevOps 工程師如何為其特定工作負載配置和擴展 Temporal worker。讀完後,你將對 worker 如何輪詢任務、執行活動、管理並行以及適應資源限制有一個清晰的心智模型。

段落總結 本段設定了清晰的學習路徑,旨在帶領讀者從零開始,層層深入地理解 Temporal worker 的內部運作機制。文章承諾不僅解釋「是什麼」,更要闡述「如何做」,特別是關於生產環境中的配置、調校與擴展,目標是為讀者建立一個完整且實用的心智模型。

一個簡單的 Temporal Worker

段落重點 在深入探討調校或擴展之前,先了解 worker 的本質。它是一個連接到 Temporal 叢集、監聽任務並執行你的程式碼以作回應的進程。一個 worker 的主要職責是輪詢 (polling) 任務和執行 (executing) 它們。它會長輪詢 Temporal 叢集,取走一個任務,執行你的程式碼,然後回報結果。這個循環會不斷重複,同時遵守並行限制和資源約束。

一個簡單的 Python worker 程式碼如下:

from temporalio.worker import Worker
from temporalio.client import Client
from my_app.workflows import MyWorkflow
from my_app.activities import send_email

async def main():
    client = await Client.connect("localhost:7233")
    worker = Worker(
        client,
        task_queue="my-task-queue",
        workflows=[MyWorkflow],
        activities=[send_email],
    )
    await worker.run()

這段程式碼連接到 Temporal 伺服器,設定一個 worker 來輪詢 my-task-queue,並註冊它可以執行的工作流程和活動。

段落總結 此段將 Temporal worker 的核心功能簡化為一個易於理解的循環:輪詢、執行、回報。透過一個極簡的 Python 程式碼範例,它展示了啟動一個 worker 的基本要素:連接客戶端、指定任務佇列、註冊處理邏輯。這為後續深入探討其內部架構奠定了基礎。

Worker 輪詢 (Polling)

段落重點 worker 如何找到事情做?答案是:Pollers (輪詢器)。輪詢器是內部的執行緒,它們長輪詢 Temporal 叢集 (特別是前端服務) 以獲取指定任務佇列上的新任務。你可以透過 max_concurrent_activity_task_pollsmax_concurrent_workflow_task_polls 來控制輪詢器的數量。

worker = Worker(
    client,
    task_queue="my-task-queue",
    workflows=[MyWorkflow],
    activities=[transcode],
    # Poller settings:
    max_concurrent_activity_task_polls=20,
    max_concurrent_workflow_task_polls=5,
)

輪詢器的數量控制了 worker 拾取新任務的速度,而不是執行這些任務的速度。通常,預設的輪詢器數量對大多數系統來說已經足夠。

段落總結 本段揭示了 worker 獲取任務的第一個內部元件:輪詢器 (Pollers)。它清晰地界定了輪詢器的職責——僅負責「獲取任務」,並說明了其數量配置會影響任務的「拾取速率」,而非執行速率,釐清了一個常見的效能誤區。

Worker 執行 — 非同步活動 (Async Activities)

段落重點 當 worker 收到一個標記為 async 的活動任務時,它會將該函式排程到 Python 的事件迴圈 (event loop) 上。這使得 async 活動非常適合 I/O 密集型工作,因為當函式等待資料庫呼叫或網路請求時,事件迴圈可以轉而執行其他任務。worker 可以保持忙碌狀態,而無需啟動額外的執行緒或進程。從 Temporal 的角度來看,無論函式是否為非同步,機制都是相同的:worker 仍然需要為長時間運行的任務發送心跳,並在完成後回報結果。

段落總結 此段解釋了 async 活動的執行原理。核心在於利用 Python 的 asyncio 事件迴圈,這使得 worker 能夠在處理 I/O 密集型任務時,以非常輕量級的方式實現高並行,避免了執行緒或進程的開銷。

Worker 執行 — 同步活動 (Sync Activities) 和執行器 (Executors)

段落重點 同步活動比較棘手,因為它們會阻塞 worker 的主執行緒。為了避免這個問題,Temporal worker 使用執行器 (executors)。執行器是一個執行緒池或進程池,可以在背景執行任務。當 worker 收到一個同步活動任務時,它會將工作交給一個 ThreadPoolExecutor。池中的每個執行緒都可以執行一個任務,而不會阻塞主事件迴圈。

大多數 worker 預設使用 ThreadPoolExecutor。對於需要更強隔離性的 CPU 密集型工作,Temporal 允許你換成 ProcessPoolExecutor。無論使用哪種執行器,流程都是一樣的:worker 分派任務,執行器執行你的函式,結果被送回 Temporal 叢集。

段落總結 本段闡明了同步活動的並行處理機制。關鍵在於使用「執行器」(預設為執行緒池),將會產生阻塞的同步任務卸載到背景執行緒中執行,從而確保 worker 的主事件迴圈保持回應,能夠繼續處理其他任務。

插槽 (Slots)、供應商 (Suppliers)、調諧器 (Tuners)

段落重點 max_concurrent_activities 參數設定了 worker 可以同時執行活動任務的硬性上限。但這個靜態限制無法根據任務類型動態調整。對於輕量的 async 任務,我們希望提高並行度;對於沉重的 sync 任務,則需要降低。

Temporal 提供了 ResourceBasedTunersResourceBasedSlotSuppliers 來解決這個問題。Slot (插槽) 是一個抽象概念,代表 worker 執行一個任務的能力。SlotSupplier 決定 worker 是否有空閒插槽來接收新任務。我們只需設定 target_memory_usagetarget_cpu_usage,Temporal SDK 就會自動監控 worker 的資源使用情況,並在拾取新任務前確保資源使用率低於目標,從而動態調整並行度。

tuner = WorkerTuner.create_resource_based(
    target_memory_usage=0.5,
    target_cpu_usage=0.5,
)
worker = Worker(
    client,
    task_queue="foo",
    tuner=tuner,
)

段落總結 此段揭示了 Temporal worker 實現動態資源管理的智慧核心。透過引入「插槽」這一抽象概念,並利用基於資源的「調諧器」,worker 能夠超越靜態的並行限制,根據即時的 CPU 和記憶體負載,智慧地調節任務的接收速率,從而實現資源利用率的最大化和系統的穩定性。

使用 KEDA 擴展 Worker Pods

段落重點 如何決定何時需要更多或更少的 worker?這正是 KEDA (Kubernetes Event-Driven Autoscaler) 的用武之地。KEDA 能夠根據外部指標擴展 Kubernetes 的部署,並且內建了對 Temporal 的支援。

KEDA 內部使用 Temporal 的 GetTaskQueueMetadata API 來獲取任務佇列的待辦事項大小 (backlog size)。你可以為這個待辦事項設定一個閾值:

metadata:
  taskQueue: my-task-queue
  threshold: "50"

如果待處理的任務超過 50 個,KEDA 將擴展 worker 的部署。當待辦事項數量持續低於閾值時,KEDA 會將 Pod 數量縮減回去。

段落總結 本段介紹了 worker 叢集層面的水平擴展策略。關鍵是利用 KEDA,將擴展決策與 Temporal 任務佇列的「積壓量」直接掛鉤。這提供了一種自動化、反應靈敏且與實際負載緊密相關的擴展機制,避免了資源浪費和任務延遲。


文章總結

這篇文章對 Temporal Worker 進行了一次由淺入深、極其詳盡的架構解剖。它從一個簡單的 Worker 定義出發,逐步揭開了其高效、穩健運作背後的內部機制,為讀者構建了一個清晰且完整的心智模型。

文章的核心貢獻在於分層次地解釋了 Worker 的運作與擴展策略:

  1. 任務獲取層:透過 Pollers 從 Temporal 叢集輪詢任務,其數量決定了任務的「拾取速率」。
  2. 任務執行層:針對不同任務類型採用不同策略。非同步活動利用 Python 的事件迴圈實現輕量級並行;同步活動則透過執行器 (Executors)(如執行緒池)在背景執行,避免阻塞主流程。
  3. 智慧調度層:超越靜態的並行限制,利用基於資源的調諧器 (Tuners)插槽 (Slots) 概念,根據即時的 CPU 和記憶體負載,動態調整可執行的任務數量,實現了單一 Worker 內部資源利用率的最大化。
  4. 叢集擴展層:在單一 Worker 之上,文章介紹了如何使用 KEDA 監控任務佇列的積壓量(backlog),來自動化地水平擴展(增減)Worker Pod 的數量,確保整個 Worker 叢集能夠應對波動的負載。

總而言之,這篇文章不僅僅解釋了 Temporal Worker 的工作原理,更提供了一套從單一程序內部優化到整個叢集動態擴展的完整、可實施的生產級解決方案。對於任何希望在生產環境中大規模使用 Temporal 的工程師來說,這都是一份寶貴的架構與實踐指南。