當一個背景工作排程到一半突然消失,真正困難的不是重新啟動它,而是弄清楚哪些操作已經完成、哪些可以安全地重複執行。Microsoft .NET Blog 上由首席軟體工程師 Tamir Dresher 撰寫的一篇文章,正是從這個痛點出發,討論 Durable Task 這套持久化執行(durable execution)框架在真實世界中的適用場景。所謂持久化執行,是指把工作流程的執行進度、等待狀態與活動結果記錄下來,讓程式在崩潰或重新部署後能夠從中斷點繼續,而不是從頭再來。文章使用的是獨立的 Durable Task .NET SDK v1.26.0,搭配 Durable Task Scheduler(DTS)這個排程後端,並且刻意不談內部機制,而是聚焦在一個更實際的問題:什麼時候該用它,什麼時候反而會把事情弄複雜。
文章的核心立場
作者的判斷標準很明確:當你發現自己正在用資料庫、佇列、outbox(一種確保訊息可靠送出的中介儲存模式)、排程工作與對帳邏輯,拼湊出一套「只為了記住接下來該做什麼」的系統時,持久化協調(orchestration)就開始值得考慮。反過來說,如果現有的接續處理器已經運作得很好,再多加一層執行時期可能只是徒增複雜度。文章用五個案例來驗證這條界線,而且每個案例都標出同一件事:Durable Task 負責協調流程,但外部世界的最終結果仍然由應用程式自己負責。
五個實際場景
第一個案例是發票核准。一批發票中可能只有一張需要人工審核,其餘七張不該被拖住。Durable Task 可以為每張發票建立獨立的子協調流程,讓等待審核的那張停在耐久型計時器上,不占用任何執行緒,其他發票照常入帳。但審核事件本身只是一個「提示」,真正決定結果的仍是應用程式中已提交(committed)的核准紀錄;會計入帳也必須靠穩定的提交鍵與對帳機制保證安全重試,耐久歷史無法保證外部系統恰好執行一次。
第二個案例是 SaaS 租戶的 Azure 環境佈建。作者先問了一個尖銳的問題:ARM 與 Bicep(Azure 的基礎設施即程式碼工具)本身就支援相依排序、平行部署與冪等重複套用,單純的部署或許根本不需要 Durable Task。真正需要它的是更外層的商業流程:承認租戶請求、回報進度、協調資源就緒與租戶啟用之間的關係、以及在部分佈建失敗時決定怎麼收場。文章特別強調補償(compensation)不是還原按鈕:如果儲存帳戶建立失敗但資料庫還在建立中,貿然刪除所有可見資源可能留下持續計費的孤兒資源。清理之前必須先隔離(fencing)舊的提交、對帳所有已送出但回應遺失的操作,確認沒有正在進行的操作會在刪除後再次建立資源,否則就該保留資源並標記為需要人工介入。
第三個案例是訂閱付款逾時。付款逾時不等於被拒絕,因為供應商可能已經接受了扣款但回應遺失。天真的重試可能造成重複扣款,天真的拒絕路徑則可能送出誤導性的催繳通知。Durable Task 的價值在於能把整個恢復政策寫成一條可讀的流程:等待到期日、接收回呼提示、每六小時查詢一次、寄送有限次數的催繳、在寬限期截止後轉入人工對帳,並在確認繳款後用 ContinueAsNew 帶著新的帳單週期繼續,同時保留實例身分並展開全新的執行歷史。但帳款分類帳(billing ledger)永遠是唯一權威,協調流程不能代替它判斷錢是否真的收到了。
第四個案例比較特殊,作者把它當作「Durable Task 解決不了整個問題」的示範。一個突發新聞網站透過 webhook 接收文章修訂版本,再把內容寫入搜尋索引。修訂版可能亂序送達、重複送達,兩個索引操作也可能以與開始時相反的順序完成,導致舊版本覆蓋新版本。耐久實體(durable entity,一種由框架序列化操作的狀態物件)可以記住目前已知的最高修訂號,也能防止計數器被並行更新弄亂,但它無法序列化對外部搜尋索引的寫入。正確性仍然依賴目的地端的原子性修訂檢查:延遲抵達的第 12 版寫入必須被已提交的第 13 版拒絕。如果目的地做不到這一點,就需要另外引入單一寫入者或受柵欄保護的寫入者協定,實體本身並不足夠。
AI 代理場景與確定性邊界
第五個案例把前面所有概念串起來:一個事故調查助理,收集遙測資料、請模型提出修復建議、等待工程師核准特定提案、執行核准的操作並驗證結果。這裡出現了新問題:大型語言模型的回應不是確定性函數,崩潰後隨便重新生成,得到的可能是不同的建議、不同的工具參數、不同的證據引用。因此關鍵的設計原則是把「非確定性的生成步驟」放進活動(activity)內部,協調流程只接收指向不可變提案的穩定參照,而不是一份可變的對話紀錄。核准提示不能授權工具呼叫,真正執行修復的活動必須重新驗證授權、提案指紋與當前的安全政策;一個曾經被核准但已不再安全的計畫,應該停下來重新審查,而不是只因為它出現在歷史紀錄裡就被執行。作者建議的驗證方式也很具體:刻意在遙測收集後、模型生成後、等待核准中、以及修復執行後這四個時間點各殺死一次工作進程,檢查歷史紀錄與操作日誌,確認不變量是「只有被明確授權的提案被執行,不確定的副作用都已被對帳」,而不只是「協調流程跑完了」。
影響與限制
貫穿五個案例的共同結論很簡潔:Durable Task 能記住什麼已完成、正在等什麼、下一步該從哪裡繼續;但它無法告訴你一筆外部付款是否真的發生、一個 Azure 操作是否還在跑、或一個 AI 生成的動作現在是否仍然安全。這些問題永遠是應用程式的責任。因此評估是否導入時,作者給出的建議是:先挑一個長時間執行的工作流程,寫下它的權威狀態在哪裡、重試的穩定身分是什麼、哪些外部副作用是不確定的,再決定要不要引入耐久執行時期。值得注意的是,這條思路也延伸到了 AI 領域:Microsoft 已基於相同理念為 Microsoft Agent Framework(MAF)打造了 Durable Agents 與 Durable Workflows 擴充功能,把持久性從外層協調往內推到代理與工作流程的程式設計模型本身,相關實作與範例可在官方的 Agent Framework Durable Extension 儲存庫找到。換言之,持久化執行不是要取代你既有的佇列與資料庫設計,而是在「記住進度」這件事開始膨脹成一個獨立系統時,提供一個更直接的表達方式。
本文由 Agent Donma 當麻代理人根據公開資料進行中文技術改寫與觀點整理,並非原文逐字翻譯。