Durable Functions 的版本管理至關重要,因為函式在應用程式的生命週期中不可避免地會被新增、移除與變更。 Durable Functions 讓你以以前無法做到的方式串接函式,這種串接影響了你處理版本管理的方式。
這篇文章能幫助你:
快速策略比較
如果你已經知道你的變更會引發問題,請使用這張表格來選擇因應策略:
| 策略 | 最適合用於 | 詳細資料 |
|---|---|---|
| 編排版本管理(建議) | 大多數應用程式都有破壞性變更。 內建執行時功能,適用於任何儲存後端。 | 跳至章節 |
| 並肩部署 | 無法使用編排版本控制,或需要透過獨立任務中心或儲存帳號完全隔離的應用程式。 | 跳至章節 |
| 停止所有執行中的實例 | 原型製作與本地開發情境中,允許失去運行中的編排。 | 跳至章節 |
Tip
如果你在尋找內建的 編排版本管理 功能,能在執行時層級自動提供版本隔離,請參見 編排版本管理。
Important
部署前,請檢查您的變更是否屬於破壞性變更:
- 你有更改活動或實體函式的 名稱、輸入類型或輸出類型 嗎?
- 你是否在協調器程式碼中新增、移除或重新排序活動、子協調工作流、計時器或外部事件的呼叫?
- 你是否重新命名或移除某個函式,而該函式可能仍被正在進行的流程呼叫?
如果你對上述任何一項回答 是肯定 的,請採用以下的 緩解策略 之一,以避免執行中編排失敗。
重大變更的類型
有幾個突破性變更的例子存在。 本文將討論最常見的類型。 這些設計的核心主題是,函式程式碼的變更會影響新舊函式的協調。
活動或實體功能簽名變更
簽章變更是指函式的名稱、輸入或輸出變更。 如果你對活動或實體函數進行此類變更,可能會損壞任何依賴該變更的協調器函數。 這種行為在型別安全的語言中特別適用。 如果您更新協調器函式來配合此變更,可能會中斷現有的執行中執行個體。
舉例來說,考慮以下的編排器函式。
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
await context.CallActivityAsync("Bar", result);
}
此函數將 Foo 的結果傳遞給 Bar。 假設你需要將 Foo 的回傳值從布林值改成字串,以支援更多樣的結果值。 結果如下所示:
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
string result = await context.CallActivityAsync<string>("Foo");
await context.CallActivityAsync("Bar", result);
}
對於所有新的 orchestrator 實例,這項變更運行良好,但可能會中斷任何正在進行中的實例。 例如,假設協調流程執行個體會呼叫名為 Foo 的函式、取回布林值,然後執行檢查點。 如果此時部署簽章變更,檢查點實例在恢復並重播呼叫 Foo時立即失敗。 此失敗發生是因為歷史表中的結果是布林值,但新程式碼嘗試將其反序列化為 String 值,導致異常行為,甚至在型別安全語言中出現執行時例外。
這個例子是函式簽名變更可能破壞現有實例的眾多方式之一。 一般而言,如果協調器需要變更呼叫函式的方式,則變更可能會有問題。
編排器邏輯變更
另一類版本控制的問題來自於更改協調器函式程式碼,改變了在執行中實例的執行路徑。
請考慮下列協調器函式:
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
await context.CallActivityAsync("Bar", result);
}
現在假設你想在兩個現有函式呼叫之間新增一個函式呼叫。
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
if (result)
{
await context.CallActivityAsync("SendNotification");
}
await context.CallActivityAsync("Bar", result);
}
此變更在 SendNotification 和 Foo 之間新增了一個對 Bar 的函式呼叫。 沒有簽名變更。 問題在於當現有實例從Bar的呼叫恢復時。 在重播期間,如果最初對 Foo 的呼叫回傳了 true,那麼編排器在重播時會呼叫 SendNotification,這不在其執行歷史中。 執行時偵測到此不一致,並因預期應看到呼叫時卻遇到SendNotification呼叫,而產生Bar錯誤。 同樣的問題也可能發生在為其他持久性操作加入 API 呼叫時,例如建立持久計時器、等待外部事件或呼叫子編排。
緩解策略
Warning
部署破壞性變更卻沒有緩解策略(「什麼都不做」方法),可能導致編排失敗且出現 非確定性編排 錯誤、無限期停留在狀態 Running ,或觸發低階執行時故障,降低效能。 部署重大變更時,務必採用以下策略之一。
編排版本管理(建議)
與本節其他策略不同,編排版本控制是 內建的執行時功能 ,提供自動版本隔離。 你不需要管理獨立的部署、任務中心或儲存帳號。 相反地,執行時本身會追蹤版本資訊,並確保協調實例能由相容的工作者處理。
使用協調流程版本設定:
- 每個協調流程執行個體在建立時都會永久地取得與其相關聯的版本。
- Orchestrator 函式可以檢查其版本並根據條件執行分支,將舊的與新的程式碼路徑維持在同一個程式碼庫中。
- 執行較新編排器功能版本的工作者仍可繼續執行由舊版本建立的編排實例。
- 執行階段會防止執行舊版本編排器函式的工作者執行新版本的編排。
此方法只需最少的設定(包含版本字串及可選的匹配策略),且與任何 儲存供應商相容。 這是需要支援重大變更同時維持 零停機部署的應用程式的推薦策略。
如需詳細的設定與實作指引,請參閱 編排版本管理。
停止所有執行中的執行個體
另一個選項是停止所有執行中的執行個體。 ** 如果你使用的是 Durable Functions 的預設 Azure 儲存體 提供者,請透過清除內部 control-queue 和 workitem-queue 佇列的內容來停止所有實例。 或者,停止函式應用程式,刪除這些佇列,然後重新啟動應用程式。 應用程式重新啟動後,佇列會自動重新建立。 之前的協調實例可能會無限期地維持在「執行中」狀態,但不會讓你的日誌被失敗訊息淹沒,也不會對你的應用程式造成任何傷害。 此方法非常適合快速原型開發,包括本地開發。
Warning
此方法需要直接存取底層儲存資源,且不適用於所有由 Durable Functions 支援的儲存提供者。
並存部署
確保破壞性變更安全部署的最萬無一失的方法,是將它們與舊版本並排部署。 你可以使用以下任一種技巧:
- 不同的儲存帳號:將所有更新部署為一個新的函式應用程式,使用不同的儲存帳號。 這完全將新版本的狀態與舊版本隔離開來。
- 不同的任務中心:部署一個新的功能應用程式,使用相同的儲存帳號,但任務 中心 名稱已更新。 此方法為新版本創造新的儲存產物,而舊版本則繼續使用現有產物。
在 Azure 進行並排部署時,你可以使用 部署槽 同時執行兩個版本,僅其中一個作為活躍的 生產 槽。 當你準備好公開新的編排邏輯時,把新版本換到製作檔位。
Note
本指南使用Azure 儲存體專屬術語,但一般適用於所有支援的 Durable Functions 儲存供應商。
Note
部署插槽交換在 HTTP 和 Webhook 觸發條件下效果最佳。 對於非 HTTP 觸發器,如佇列或事件中心,觸發器定義應源自於交換操作中更新的 應用程式設定 。
下一步
- 瞭解零停機時間部署策略
了解Durable Functions 的儲存提供者