分享方式:


Azure 儲存體提供者 (Azure Functions)

本文件說明 Azure 儲存體提供者 Durable Functions 的特性,並著重於效能和延展性的層面。 預設提供者為 Azure 儲存體提供者。 它會將執行個體的狀態和佇列儲存於 Azure 儲存體 (傳統) 帳戶中。

注意

關於 Durable Functions 支援的儲存體提供者及相互比較,如需詳細資訊,請參閱 Durable Functions 儲存體提供者文件。

在 Azure 儲存體提供者中,所有函式執行皆由 Azure 儲存體佇列所驅動。 協調流程、實體狀態及歷程記錄皆儲存於 Azure 資料表中。 Azure Blob 和 Blob 租用可將協調流程執行個體和實體分散到多個應用程式執行個體 (又稱為「背景工作」角色,或簡稱 VM)。 本節詳述各種 Azure 儲存體成品,及其如何影響效能和可擴縮性。

儲存體表示法

工作中樞會永久保存所有執行個體狀態以及所有訊息。 如需這些用來追蹤協調流程進度方式的快速概觀,請參閱工作中樞執行範例

Azure 儲存體提供者使用下列元件來代表儲存體中的工作中樞:

  • 介於兩到三個 Azure 資料表之間。 兩個資料表可用來代表歷程記錄和執行個體狀態。 如果已啟用資料表分割管理員,則會引進第三個資料表來儲存分割資訊。
  • 一個 Azure 佇列儲存該活動訊息。
  • 一或多個 Azure 佇列會儲存執行個體訊息。 這些所謂的 控制佇列 皆代表根據執行個體識別碼的雜湊,以指派所有執行個體訊息子集的 分割區
  • 租用 blob 和/或大型訊息使用對象的一些額外 blob 容器。

例如,取代PartitionCount = 4命名為xyz的工作中樞包括下列佇列和資料表:

此圖顯示 4 個控制佇列 Azure 儲存體 提供者記憶體組織。

接下來,我們更詳細地描述這些元件及其所扮演的角色。

記錄資料表

[歷程記錄] 資料表是 Azure 儲存體資料表,含有工作中樞內所有協調流程執行個體的歷程記錄事件。 此資料表的名稱格式為 TaskHubNameHistory。 隨著執行個體執行,此資料表中會新增資料列。 此資料表的資料分割索引鍵衍生自協調流程的執行個體識別碼。 執行個體識別碼預設為隨機,可確保 Azure 儲存體中的內部分割區達到最佳分佈。 此資料表的資料列索引鍵是用來排序歷程記錄事件的序號。

當協調流程執行個體需要執行時,將透過單一資料表分割區內的範圍查詢,將「歷程記錄」資料表的對應資料列載入記憶體中。 這些「歷程記錄事件」會接著重新顯示為協調器函式程式碼,使其回到先前的檢查點狀態。 以這種方式使用執行歷程記錄重建狀態,會受事件來源模式所影響。

提示

儲存在歷程記錄資料表中的協調流程資料,包括來自活動和子協調器函式的輸出承載。 來自外部事件的承載也儲存在歷程記錄資料表中。 因為每次需要執行協調器時會將完整歷程記錄載入記憶體中,太大的歷程記錄可能導致給定的 VM 面臨很大的記憶體壓力。 將大型協調流程分割成多個子協調流程,或將活動及其呼叫的子協調器函式所傳回的輸出縮小,就可降低協調流程歷程記錄的長度和大小。 或者,您可以降低每個 VM 的並行節流來減少記憶體使用量,以限制同時載入記憶體中的協調流程數目。

執行個體資料表

執行個體資料表包含工作中樞內所有協調流程和實體執行個體的狀態。 隨著執行個體的建立,此資料表中會新增資料列。 此資料表的資料分割索引鍵是協調流程執行個體識別碼或實體索引鍵,而資料列索引鍵是空字串。 每個協調流程或實體執行個體各有一個資料列。

此資料表用於滿足來自程式碼的執行個體查詢要求,以及狀態查詢 HTTP API 呼叫。 它終於與先前所述的 [歷程記錄] 資料表內容保持一致。 以這種方式使用不同的 Azure 儲存體資料表有效地滿足執行個體查詢作業,會受到命令和查詢責任隔離 (CQRS) 模式所影響。

提示

「執行個體」資料表經過分割可儲存數百萬個協調流程執行個體,而完全不會明顯影響執行階段效能或規模。 不過,執行個體數目可能明顯影響多重執行個體查詢效能。 若要控制儲存在這些資料表中的資料量,請考慮定期清除舊的執行個體資料

分割資料表

注意

只有在啟用 Table Partition Manager 時,此資料表才會顯示在工作中樞中。 若要套用,請在應用程式的 host.json 中設定 useTablePartitionManagement

分割資料表會儲存 Durable Functions 應用程式的分割狀態,並用來將分割分散到您應用程式的背景工作角色。 每個分割都有一個資料列。

佇列

協調器、實體和活動函式都由函數應用程式工作中樞內的內部佇列所觸發。 以這種方式使用佇列會提供可靠的「至少一次」訊息傳遞保證。 Durable Functions 中有兩種佇列:控制佇列工作項目佇列

工作項目佇列

在 Durable Functions 中,每個工作中樞各有一個工作項目佇列。 這是基本佇列,運作方式類似於 Azure Functions 中的其他任何 queueTrigger 佇列。 此佇列用來觸發無狀態「活動函式」,做法是一次從佇列中清除一則訊息。 上述每則訊息都包含活動函式輸入和其他中繼資料,例如要執行哪個函式。 當 Durable Functions 應用程式擴增至多個 VM 時,這些 VM 全部會從工作項目佇列中爭取工作。

控制佇列

在 Durable Functions 中,每個工作中樞都有多個「控制佇列」。 「控制佇列」比簡單的工作項目佇列更複雜。 控制佇列用來觸發具狀態的協調器和實體函式。 由於協調器和實體函式執行個體是具狀態單一個體,每個協調流程或實體每次只由一個背景工作角色處理。 為了遵守此限制,每個協調流程執行個體或實體都指派給單一控制佇列。 這些控制佇列由各背景工作角色平均負擔,以確保每個佇列每次只由一個背景工作角色處理。 後續各節對此行為有更詳細的說明。

控制佇列包含各種不同的協調流程生命週期訊息類型。 例如,協調器控制訊息、活動函式「回應」訊息,以及計時器訊息。 將有 32 則訊息會在單一輪詢中從控制佇列中清除。 這些訊息包含承載資料以及中繼資料,包括適用於哪個協調流程執行個體。 如果有多則已從佇列中清除的訊息適用於相同的協調流程執行個體,系統會將這些訊息當作一個批次處理。

控制佇列訊息由背景執行緒持續輪詢。 每次佇列輪詢的批次大小由 host.json 中的 controlQueueBatchSize 設定控制,預設為 32 (Azure Queues 支援的最大值)。 記憶體中最多可緩衝多少個預先擷取的控制佇列訊息,由 host.json 中的 controlQueueBufferThreshold 設定控制。 controlQueueBufferThreshold 的預設值隨各種因素而有所不同,包括主控方案的類型。 如需這些設定的詳細資訊,請參閱 host.json 結構描述文件。

提示

提高 controlQueueBufferThreshold 的值可讓單一協調流程或實體處理事件越快。 但提高此值也會導致耗用較多記憶體。 耗用較多記憶體有一部分是因為從佇列提取較多訊息,有一部分是因為將較多協調流程歷程記錄存入記憶體中。 因此,降低 controlQueueBufferThreshold 的值可以有效減少記憶體使用量。

佇列輪詢

持久工作延伸模組實作隨機指數型倒退演算法,以降低閒置佇列輪詢對儲存體交易成本的影響。 執行階段找到訊息時會立即檢查其他訊息。 找不到訊息時,則會等一段時間後再重試。 如果後來嘗試取得佇列訊息都失敗,等候時間就越來越久,直到達到等待時間上限,預設為 30 秒。

可透過 host.json 檔案中的 maxQueuePollingInterval 屬性來設定最長輪詢延遲。 此屬性的值設定越大可能導致訊息處理延遲越久。 只有在閒置多次之後,才會延遲越久。 此屬性的值設定越小,由於儲存體交易增加,可能導致儲存體成本越高

注意

在 Azure Functions 取用和進階方案中執行時,Azure Functions 縮放控制器每隔 10 秒輪詢一次每個控制和工作項目佇列。 此額外輪詢有其必要,如此才能決定何時啟動函數應用程式執行個體,並做出縮放決策。 本文撰稿時,此 10 秒間隔固定,無法設定。

協調流程延遲啟動

ExecutionStarted 訊息放入工作中樞的其中一個控制佇列,就會啟動協調流程執行個體。 在某些情況下,在協調流程排定執行與實際開始執行之間可能延遲幾秒。 在此期間,協調流程執行個體停留在 Pending 狀態。 此延遲有兩個可能的原因:

  • 待處理的控制佇列:如果此執行個體的控制佇列包含大量訊息,可能要花一些時間,執行階段才會收到和處理 ExecutionStarted 訊息。 當協調流程同時處理許多事件時,可能會有待處理訊息。 進入控制佇列的事件包括協調流程啟動事件、活動完成、持久計時器、終止和外部事件。 如果在正常情況下發生此延遲,請考慮建立具有大量分割區的新工作中樞。 設定越多分割區,執行階段就會建立越多控制佇列來分散負載。 每個分割區與控制佇列的比例為 1:1,最多 16 個分割區。

  • 退倒輪詢延遲:協調流程延遲的另一個常見原因是前述控制佇列的倒退輪詢行為。 但只有在應用程式擴增為兩個以上的執行個體時,才會發生此延遲。 如果只有一個應用程式執行個體,或啟動協調流程的應用程式執行個體也是輪詢目標控制佇列的同一個執行個體,則不會有佇列輪詢延遲。 如先前所述,更新 host.json 設定可以減少倒退輪詢延遲。

Blob

在大多案例中,Durable Functions 不會使用 Azure 儲存體 Blob 保存資料。 但佇列和資料表有大小限制,可防止 Durable Functions 將所有必要資料保存至儲存體資料列或佇列訊息。 例如,大於 45 KB 的資料必須保存至佇列,但序列化後,Durable Functions 會壓縮資料並改為儲存在 Blob 中。 以這種方式將資料保存至 Blob 儲存體時,Durable Function 會在資料表資料列或佇列訊息中,儲存該 Blob 的參考。 需要擷取資料時,Durable Functions 會自動從 Blob 擷取資料。 這些 Blob 儲存在 Blob 容器 <taskhub>-largemessages 中。

效能考量

大型訊息的額外壓縮和 Blob 作業步驟可能耗費大量 CPU 和 I/O 延遲成本。 此外,Durable Functions 必須在記憶體載入保存的資料,而且可能同時執行許多不同的函式。 因此,保存大型資料承載也可能導致高記憶體使用量。 若要將記憶體額外負荷降到最低,請考慮手動保存大型資料承載 (例如,在 Blob 儲存體),並改為傳遞此資料的參考。 如此一來,協調器函式重新執行期間,您的程式碼可以只在需要避免多餘負載時,載入資料。 但建議您儲存承載至本地磁碟,因為不保證磁碟上為可用狀態,而且函式在整個存留期中,可能在不同的 VM 上執行。

儲存體帳戶選取

Durable Functions 使用的佇列、資料表和 Blob 是在設定的 Azure 儲存體帳戶中建立。 在 host.json 檔案中,可使用 durableTask/storageProvider/connectionStringName 設定 (或 Durable Functions 1.x 中的 durableTask/azureStorageConnectionStringName 設定) 來指定要使用的帳戶。

Durable Functions 2.x

{
  "extensions": {
    "durableTask": {
      "storageProvider": {
        "connectionStringName": "MyStorageAccountAppSetting"
      }
    }
  }
}

Durable Functions 1.x

{
  "extensions": {
    "durableTask": {
      "azureStorageConnectionStringName": "MyStorageAccountAppSetting"
    }
  }
}

如果未指定,則會使用預設 AzureWebJobsStorage 儲存體帳戶。 不過,對於重視效能的工作負載,建置您設定非預設的儲存體帳戶。 Durable Functions 會密集使用 Azure 儲存體,而使用專用儲存體帳戶會隔離 Durable Functions 儲存體使用量與 Azure Functions 主機的內部使用量。

注意

使用 Azure 儲存體提供者時,需要標準一般用途 Azure 儲存體帳戶。 不支援其他所有儲存體帳戶類型。 強烈建議 Durable Functions 使用舊版 v1 一般用途儲存體帳戶。 就 Durable Functions 工作負載而言,較新的 v2 儲存體帳戶明顯太浪費成本。 如需 Azure 儲存體帳戶類型的詳細資訊,請參閱儲存體帳戶概觀一文。

協調器向外延展

雖然可隨意增加更多 VM 來無限擴增活動函式,但個別協調器執行個體和實體只能存在於單一分割區,而且分割區數目上限受制於 host.json 中的 partitionCount 設定。

注意

一般而言,協調器函式是輕巧的設計,應該不需要大量運算能力。 因此,沒必要為了提高協調流程的輸送量而建立大量的控制佇列分割區。 大部分繁重的工作應在無狀態活動函式中進行,這還可以無限制地相應放大。

控制佇列數目會定義於 host.json 檔案中。 下列 host.json 範例程式碼片段將 durableTask/storageProvider/partitionCount 屬性 (或 Durable Functions 1.x 中的 durableTask/partitionCount) 設定為 3。 請注意,有多少控制佇列就有多少分割區。

Durable Functions 2.x

{
  "extensions": {
    "durableTask": {
      "storageProvider": {
        "partitionCount": 3
      }
    }
  }
}

Durable Functions 1.x

{
  "extensions": {
    "durableTask": {
      "partitionCount": 3
    }
  }
}

一個工作中樞可設定為有 1 到 16 個資料分割。 若未指定,則預設資料分割計數為 4

在低流量情節下,應用程式會縮減,因此由少數背景工作角色來管理分割區。 以下圖為例。

相應縮小協調流程圖表

在上圖中,可看到協調器 1 到 6 跨分割區平衡負載。 同樣地,分割區 (例如活動) 跨背景工作角色平衡負載。 不論啟動多少個協調器,分割區都跨背景工作角色平衡負載。

如果您在 Azure Functions 取用或彈性進階方案上執行,或已設定根據負載自動縮放,則流量越多會配置越多背景工作角色,最終,分割區會跨所有背景工作角色平衡負載。 如果持續擴增,最終會由單一背景工作角色管理每個分割區。 反之,活動繼續跨所有背景工作角色平衡負載。 如下圖所示。

第一個向外延展協調流程圖表

「任何時候」的同時「作用中」協調流程數目上限,等於配置給應用程式的背景工作角色數目「乘以」maxConcurrentOrchestratorFunctions 的值。 當分割區全部跨背景工作角色擴增時,此上限會更準確。 完全擴增時,由於每個背景工作角色只有單一 Functions 主控件執行個體,「作用中」同時協調器執行個體的數目上限,等於分割區數目「乘以」maxConcurrentOrchestratorFunctions 的值。

注意

在此,「作用中」表示協調流程或實體載入記憶體中,並處理「新的事件」。 如果協調流程或實體等候更多事件,例如活動函式的傳回值,則會從記憶體卸載,不再視為「作用中」。 隨後,只有當有新的事件要處理時,協調流程和實體才會重新載入記憶體中。 單一 VM 上可執行的協調流程或實體「總數」,即使全都處於「執行中」狀態,也沒有真正的數目上限。 唯一的限制是「同時作用中」的協調流程或實體執行個體數目。

下圖以完全擴增的情節為例,其中增加更多協調器,但有些為非作用中,以灰色顯示。

第二個相應放大協調流程圖表

在擴增期間,控制佇列租用可能重新散發至各 Functions 主控件執行個體,以確保分割區平均分散。 這些租用在內部實作為 Azure Blob 儲存體租用,確保任何個別協調流程執行個體或實體,每次只在單一主控件執行個體上執行。 如果工作中樞已設定三個分割區 (亦即三個控制佇列),則協調流程執行個體和實體可以跨完全持有這三個租用的主控件執行個體平衡負載。 您可以新增更多虛擬機器,以增加容量來執行活動函式。

下圖說明在向外延展環境中,Azure Functions 主機與儲存體實體之間的互動方式。

縮放圖表

如上圖所示,所有虛擬機器會爭奪工作項目佇列上的訊息。 不過,只有三個虛擬機器可以從控制佇列中取得訊息,每個虛擬機器會鎖定單一控制佇列。

協調流程執行個體和實體分散於所有控制佇列執行個體。 協調流程的執行個體識別碼,或實體名稱和金鑰組,經過雜湊後決定如何散發。 協調流程執行個體識別碼預設為隨機 GUID,可確保執行個體平均分散於所有控制佇列。

一般而言,協調器函式是輕巧的設計,應該不需要大量運算能力。 因此,沒必要為了提高協調流程的輸送量而建立大量的控制佇列分割區。 大部分繁重的工作應在無狀態活動函式中進行,這還可以無限制地相應放大。

延長工作階段

延長工作階段為一快取機制,即使訊息處理已完成,仍可將協調流程和實體保留於記憶體中。 啟用延長工作階段後,通常可減少基礎耐久存放區的 I/O 並提升整體輸送量。

您可以在 host.json 檔案中將 durableTask/extendedSessionsEnabled 設定為 true,以啟用延長工作階段。 durableTask/extendedSessionIdleTimeoutInSeconds 設定可用來控制閒置工作階段在記憶體中停留多久:

Functions 2.0

{
  "extensions": {
    "durableTask": {
      "extendedSessionsEnabled": true,
      "extendedSessionIdleTimeoutInSeconds": 30
    }
  }
}

Functions 1.0

{
  "durableTask": {
    "extendedSessionsEnabled": true,
    "extendedSessionIdleTimeoutInSeconds": 30
  }
}

請注意此設定有兩個可能的缺點:

  1. 因為閒置執行個體未儘快從記憶體卸載,整體函數應用程式記憶體使用量會增加。
  2. 如果有許多同時、相異、短期協調器或實體函式執行,整體輸送量可能降低。

例如,如果 durableTask/extendedSessionIdleTimeoutInSeconds 設定為 30 秒,則執行不到 1 秒的短期協調器或實體函式片段仍會佔用記憶體 30 秒。 還會耗用前述的 durableTask/maxConcurrentOrchestratorFunctions 配額,可能導致其他協調器或實體函式無法執行。

接下來幾節說明延伸工作階段對協調器和實體函式的具體影響。

注意

目前只有 .NET 語言 (例如 C# 或 F#) 支援延伸工作階段。 針對其他平台將 extendedSessionsEnabled 設定為 true 可能導致執行階段問題,例如幕平白無故無法執行由活動和協調流程觸發的函式。

協調器函式重新執行

如先前所述,使用 [歷程記錄] 資料表的內容可重新執行協調器函式。 根據預設,每次從控制佇列中清除一批訊息時,就會重新執行協調器函式程式碼。 即使您使用展開、收合模式,並等候所有工作完成 (例如,使用 .NET 的 Task.WhenAll()、JavaScript 的 context.df.Task.all() 或 Python 的 context.task_all()),隨著時間處理幾批工作回應後就會重新執行。 啟用延長工作階段後,協調器函式執行個體在記憶體中停留更久,不必重新執行完整歷程記錄即可處理新訊息。

下列情況中最常看到延長工作階段可改善效能:

  • 同時執行的協調流程執行個體數目不多。
  • 協調流程有大量的循序動作 (例如數百個活動函式呼叫) 快速完成。
  • 協調流程展開和收合大量幾乎同時完成的動作。
  • 協調器函式需要處理大型訊息或執行任何 CPU 密集資料處理。

在其他所有情況下,通常看不出協調器函式可改善效能。

注意

只有在協調器函式經過完整開發及測試之後,才能使用這些設定。 主動重新執行預設行為有利於開發階段偵測協調器函式程式碼條件約束違規,因此預設為停用。

效能目標

下列資料表顯示效能和調整一文中的效能目標一節所述案例預期輸送量之最大數目

「執行個體」是指在 Azure App Service 中單一小型 (A1) VM 上執行之協調器函式的單一執行個體。 在所有情況下,假設已啟用擴充工作階段。 實際結果可能會因函式程式碼所執行的 CPU 或 I/O 工作而有所不同。

案例 輸送量上限
循序活動執行 每個執行個體每秒 5 個活動
平行活動執行 (展開傳送) 每個執行個體每秒 100 個活動
平行回應處理 (收合傳送) 每個執行個體每秒 150 個回應
外部事件處理 每個執行個體每秒 50 個事件
實體作業處理 每秒 64 個作業

如果您未看見您預期的輸送量數字,而且您的 CPU 和記憶體使用狀況似乎良好,請查看原因是否與儲存體帳戶的健康情況相關。 Durable Functions 擴充功能可能為 Azure 儲存體帳戶帶來大量負載,而極高的負載可能導致儲存體帳戶節流。

提示

在某些情況下,在 controlQueueBufferThresholdhost.json 中提高 設定的值可大幅增加外部事件、活動展開和實體作業的輸送量。 如果此值超過預設,則 Durable Task Framework 儲存體提供者會以更多記憶體來更積極預先擷取這些事件,也就縮短從 Azure 儲存體控制佇列中清除訊息時的延遲。 如需詳細資訊,請參閱 host.json 參考文件。

高輸送量處理

對於 Durable Functions 在理論上的效能和可擴縮性上限,Azure 儲存體後端的架構已設有一定的限制。 如果測試指出 Azure 儲存體上的 Durable Functions 未滿足輸送量需求,您應該考慮改用 Durable Functions 的 Netherite 儲存體提供者

若要比較各種基本案例可達成的輸送量,請參閱 Netherite 儲存體提供者之基本案例文件內容。

Netherite 儲存體後端由 Microsoft Research 設計和開發。 除了 Azure 分頁 Blob,還使用 Azure 事件中樞FASTER 資料庫技術。 Netherite 設計可處理的協調流程和實體輸送量遠高於其他提供者。 在某些基準情節中,顯示的輸送量比預設 Azure 儲存體提供者還多一個數量級。

關於 Durable Functions 支援的儲存體提供者及相互比較,如需詳細資訊,請參閱 Durable Functions 儲存體提供者文件。

下一步