Durable Functions (Azure Functions) 中的版本控制

在應用程式的存留期之中,必然會新增、移除和變更函式。 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 的傳回值從布林值變更為字串,以支援更多種不同的結果值。 結果如下所示:

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string result = await context.CallActivityAsync<string>("Foo");
    await context.CallActivityAsync("Bar", result);
}

在協調器函式的所有新執行個體中,此變更不會有問題,但可能會中斷任何執行中的執行個體。 例如,假設協調流程執行個體會呼叫名為 Foo 的函式、取回布林值,然後執行檢查點。 如果此時部署簽章變更,則通過檢查點的執行個體在繼續並重新呼叫 Foo 時會立刻失敗。 發生此失敗的原因是歷程記錄資料表的結果為布林值,但新的程式碼嘗試將其還原序列化為字串值,因而導致非預期的行為,或甚至是型別安全語言的執行階段例外狀況。

此範例只是函式簽章變更可能中斷現有執行個體的各種方式之一。 一般而言,如果協調器需要變更呼叫函式的方式,則變更就很可能會有問題。

變更協調器邏輯

另一類版本設定問題起因於變更協調器函式程式碼,因而導致進行中執行個體的執行路徑發生變更。

假設有下列協調器函式:

[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);
}

此變更會在 FooBar 之間新增呼叫 SendNotification 函式。 簽章不變。 當現有的執行個體在呼叫 Bar 之後繼續時,就發生問題。 在重新執行期間,如果原始呼叫 Foo 傳回 true,則協調器重新執行將呼叫 SendNotification,但這不在其執行歷程記錄中。 執行階段會偵測到此不一致並引發「不具決定性的協調流程」錯誤,因為其預期要看到對 Bar 的呼叫,但卻遇到對 SendNotification 的呼叫。 將 API 呼叫新增至其他耐久性作業 (例如,建立耐久性計時器、等候外部事件、呼叫子協調流程等等) 時,可能會發生相同類型的問題。

風險降低策略

以下是解決版本控制問題的一些策略:

  • 不執行任何動作 (不建議)
  • 停止所有執行中的執行個體
  • 並存部署

不執行任何動作

最簡單的版本設定方法是不執行任何動作,並讓進行中的協調流程執行個體失敗。 視變更類型而定,可能會發生下列類型的失敗。

  • 協調流程可能會失敗,並發生不具決定性的協調流程錯誤。
  • 協調流程可能無限期停滯,並回報 Running 狀態。
  • 如果移除函式,嘗試呼叫該函式的任何函式可能都會失敗並發生錯誤。
  • 如果函式在排程執行之後移除,則應用程式可能會在耐久性工作架構引擎中遇到低階執行階段失敗,其可能導致嚴重的效能降低。

由於這些潛在失敗,因此不建議使用「不執行任何動作」策略。

停止所有執行中的執行個體

另一個選項是停止所有執行中的執行個體。 如果您使用預設適用於 Durable Functions 的 Azure 儲存體提供者,則可透過清除內部 control-queueworkitem-queue 佇列的內容來停止所有執行個體。 您也可以停止函數應用程式、刪除這些佇列,然後再次重新啟動應用程式。 重新啟動應用程式之後,將自動重新建立佇列。 先前的協調流程執行個體可能會無限期地維持「執行中」狀態,但不會因為失敗訊息而使您的記錄變得雜亂,或對您的應用程式造成任何損害。 此方法很適合用於快速原型開發,包括本機開發。

注意

此方法需要直接存取基礎儲存體資源,而且可能不適合 Durable Functions 支援的所有儲存體提供者。

並存部署

為了確保安全地部署重大變更,將重大變更與舊版並存部署就能萬無一失。 您可以採取下列任何技術:

  • 將所有更新部署為全新函式,讓現有函式保持原狀。 由於以遞迴方式對新函式版本的呼叫端進行更新時所涉及的複雜度,因此通常不建議這樣做。
  • 以不同的儲存體帳戶,將所有更新部署為新的函式應用程式。
  • 使用相同的儲存體帳戶,但使用更新的工作中樞名稱,來部署函數應用程式的新複本。 這會導致建立可供新版應用程式使用的新儲存體成品。 舊版應用程式將繼續使用先前的儲存體成品集來執行。

並存部署是針對部署新版函數應用程式建議的技術。

注意

這個適用於並存部署策略的指引會使用 Azure 儲存體特定條款,但通常會套用至所有支援的 Durable Functions 儲存體提供者

部署位置

在 Azure Functions 或 Azure App Service 中進行並存部署時,建議您將新版函數應用程式部署至新的部署位置。 部署位置可讓您並存執行函式應用程式的多個複本,而且其中只有一個是作用中的「生產」位置。 當您準備將新的協調流程邏輯公開到現有的基礎結構時,只要將新的版本調換到生產位置即可,就是這麼簡單。

注意

當您對協調器函式使用 HTTP 和 Webhook 觸發程序時,此策略最理想。 對於非 HTTP 觸發程序 (例如,佇列或事件中樞),應該從應用程式設定衍生觸發程序定義,此設定會隨著交換作業而更新。

下一步