在應用程式的存留期內,不可避免地會新增、移除和變更函式。 Durable Functions 允許以先前不可能的方式將函式鏈結在一起,而此鏈結會影響如何處理版本控制。
重大變更的類型
有幾個重大變更的例子需要注意。 本文討論最常見的問題。 它們背後的主要主題是,新的和現有的函式協調流程都會受到函式程式碼變更的影響。
變更活動或實體函式簽章
簽章變更是指函式的名稱、輸入或輸出變更。 如果對活動或實體函式進行這種變更,它可能會中斷相依的任何協調器函式。 這對於具型別安全性的語言尤其如此。 如果您更新協調器函式來配合此變更,可能會中斷現有的執行中執行個體。
例如,假設我們有下列協調器函式。
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
await context.CallActivityAsync("Bar", result);
}
此簡單函式會取得 Foo 的結果,並將其傳遞至 Bar。 假設我們需要將 Foo 的傳回值從布爾值變更為 String,以支援更廣泛的結果值。 結果如下所示:
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
string result = await context.CallActivityAsync<string>("Foo");
await context.CallActivityAsync("Bar", result);
}
在協調器函式的所有新執行個體中,此變更不會有問題,但可能會中斷任何執行中的執行個體。 例如,假設協調流程執行個體會呼叫名為 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);
}
這項變更會將新的函式呼叫新增至 Foo 與 Bar 之間的 SendNotification。 沒有任何簽章變更。 當已有的實例從呼叫 Bar 恢復時,就會發生問題。 在重播過程中,如果對 Foo 的原始呼叫返回 true,則協調器在重播時會調用 SendNotification,而這次調用不在它的執行歷程記錄中。 執行階段會偵測到此不一致並引發「不具決定性的協調流程」錯誤,因為其預期要看到對 Bar 的呼叫,但卻遇到對 SendNotification 的呼叫。 將 API 呼叫新增至其他耐久作業時,可能會發生相同類型的問題,例如建立長期定時器、等候外部事件、呼叫子協調流程等。
風險降低策略
以下是處理版本控制挑戰的一些策略:
- 不做任何動作(不建議)
- 協調流程版本設定 (在大部分情況下建議)
- 停止所有執行中的執行個體
- 並存部署
不執行任何動作
最簡單的版本設定方法是不執行任何動作,並讓進行中的協調流程執行個體失敗。 視變更類型而定,可能會發生下列類型的失敗。
- 協調流程可能會失敗,並出現 非確定性協調流程 錯誤。
- 協調流程可能無限期停滯,並回報
Running狀態。 - 如果移除函式,任何嘗試呼叫它的函式都可能會失敗併發生錯誤。
- 如果函式在預定執行之後移除,則應用程式可能會在耐久任務框架引擎中遇到運行時間低級故障,可能會導致嚴重效能衰退。
由於這些潛在的失敗,不建議使用「不執行任何動作」策略。
編排版本控制
備註
協調流程版本設定目前為公開預覽狀態。
協調流程版本控制功能可讓不同版本的協調流程共存並同時執行,而不會發生衝突和非決定性問題,讓您能夠在不手動介入的情況下部署更新,同時允許即時協調流程實例完成。
使用協調流程版本設定:
- 每個協調流程執行個體在建立時都會永久地取得與其相關聯的版本
- 協調器函式可據以檢查其版本和分支執行
- 執行較新版協調器函式的背景工作角色可以繼續執行舊版所建立的協調流程執行個體
- 執行時間會阻止執行舊版本協調器函式的工作角色執行新版本的協調作業。
對於需要支援中斷性變更,同時維護 零停機部署的應用程式,建議採用此策略。
如需詳細的設定和實作指引,請參閱 Durable Functions 中的協調流程版本控制。
停止所有執行中的執行個體
另一個選項是停止所有執行中的執行個體。 如果您使用預設適用於 Durable Functions 的 Azure 儲存體提供者,則可透過清除內部 control-queue 和 workitem-queue 佇列的內容來停止所有執行個體。 或者,您可以停止函式應用程式、刪除這些佇列,然後再次重新啟動應用程式。 應用程式重新啟動後,系統會自動重新建立佇列。 先前的協調流程實例可能會無限期地維持在「執行中」狀態,但不會將您的記錄與失敗訊息雜亂,或對您的應用程式造成任何傷害。 這種方法非常適合用於快速原型設計,包括本地開發。
備註
此方法需要直接存取基礎記憶體資源,而且可能不適用於 Durable Functions 支援的所有記憶體提供者。
並存部署
若要確保將破壞性變更安全地部署,最可靠的方式是將它們與舊版本一起部署。 這可以使用下列任何技術來完成:
- 將所有更新部署為全新的功能,保持現有的功能 as-is不變。 通常不建議這樣做,因為在遞迴更新新函式版本的調用者時會涉及複雜性。
- 以不同的儲存體帳戶,將所有更新部署為新的函式應用程式。
- 使用相同的儲存體帳戶,但使用更新的工作中樞名稱,來部署函數應用程式的新複本。 這會導致建立可供新版應用程式使用的新儲存體成品。 舊版的應用程式可以繼續運行,並使用先前的一組儲存檔案。
並存部署是針對部署新版函數應用程式建議的技術。
備註
此併行部署策略的指導方針使用 Azure 記憶體特定詞彙,但通常適用於所有支援的 Durable Functions 記憶體提供者。
部署位置
在 Azure Functions 或 Azure App Service 中並存部署時,建議您將新版本的函式應用程式部署至新的 部署位置。 部署位置可讓您並存執行函式應用程式的多個複本,而且其中只有一個是作用中的「生產」位置。 當您準備將新的協調流程邏輯公開到現有的基礎結構時,只要將新的版本調換到生產位置即可,就是這麼簡單。
備註
當您針對協調器函式使用 HTTP 和 Webhook 觸發程式時,此策略最適用。 對於非 HTTP 觸發程序 (例如,佇列或事件中樞),應該從應用程式設定衍生觸發程序定義,此設定會隨著交換作業而更新。