Durable Functions 是 Azure Functions 的擴充,提供一種在無伺服器運算環境中執行有狀態函式的方法。 在耐用函式應用程式中,你可以使用 編排器函式 來協調其他耐用函式的執行。 Orchestrator 函數具有下列特性:
- 它們透過程序化程式碼定義函式工作流程。 不需要宣告式架構或設計工具。
- 它們可以同步或非同步呼叫其他持久函式。 被調用函式的輸出可以儲存為本地變數。
- 它們設計得耐用且可靠。 當函式呼叫
awaitoryield運算子時,執行進度會自動儲存為檢查點。 當程序回收或虛擬機重啟時,本地狀態不會遺失。 - 它們的運行時間可能會很長。 編 排實例 的總壽命可以是幾秒、幾天或幾個月,或者實例可以設定為永不結束。
本文概述協調器功能,以及它們如何協助您解決各種應用程式開發挑戰。 關於耐用功能應用程式中可用的功能類型,請參閱 耐用功能類型與功能。
協調流程身分識別
每個編排實例例都有一個實例識別碼,也稱為實例ID。 預設情況下,每個實例 ID 都是自動產生的全球唯一識別碼(GUID)。 不過,執行個體 ID 也可以是任何使用者產生的字串值。 每個協調流程執行個體識別碼在 工作中樞內都必須是唯一的。
以下規則適用於實例識別碼:
- 字元必須介於1到100字之間。
- 它們不能以
@開始。 - 它們不得包含
/、\、#或?字元。 - 它們不得包含控制字元。
備註
我們通常建議盡可能使用自動產生的實例 ID。 使用者產生的實例 ID 適用於協調實例與外部應用程式特定實體(如採購單或文件)之間有一對一對應的情況。
此外,字元限制規則的實際執行方式也會因應用程式使用的 儲存服務提供者 而異。 為確保行為正確與相容性,我們強烈建議您遵守上述實例識別規則。
協調流程的執行個體識別碼是大部分執行個體管理作業的必要參數。 實例識別對診斷也很重要。 例如,當你在 Application Insights 中搜尋編排追蹤資料 以進行故障排除或分析時,就會使用它們。 因此,我們建議您將產生的實例 ID 儲存到外部位置,方便日後參考。 地點的例子包括資料庫或應用程式日誌。
Reliability
編排器功能使用 事件溯源 設計模式,以協助穩定維持執行狀態。 長期工作架構會使用僅限附加的存放區來記錄函式協調流程所採取的一系列完整動作,而不會直接存放協調流程的目前狀態。 追加存儲與轉存完整執行時狀態相比有許多優點。 好處包括提高效能、可擴展性和回應能力。 您還可以獲得交易資料的最終一致性以及完整的稽核追蹤和歷史記錄。 稽核追蹤支援可靠的補償行為。
Durable Functions 會以透明的方式使用事件來源。 在幕後,協調器函式使用 C# 中的await 運算子,以及在 JavaScript 和 Python 中的yield 運算子。 這些運算元將編排執行緒的控制權交還給 Durable Task Framework 調度器。 在 Java 函式中,沒有特殊的語言關鍵字。 相反地,呼叫 .await() 任務會透過自訂實例 Throwable將控制權交還給派遣器。 接著,調度器會將由編排器函式排程的任何新動作提交到儲存裝置。 動作範例包括呼叫一個或多個子函式或排程一個持久定時器。 透明認可動作會將所有新事件附加至儲存體,藉此更新協調流程執行個體的執行歷程記錄,這與僅限附加的記錄類似。 同樣地,提交動作會在儲存體中建立訊息以排程實際工作。 此時,可以從記憶體卸載協調器函式。 根據預設,Durable Functions 會使用 Azure 儲存體作為其執行階段狀態存放區,但 也支援其他儲存體提供者。
當協調流程函式有更多工作要做時 (例如,收到回應訊息或持久計時器到期) ,協調器會喚醒並從一開始重新執行整個函式,以重建本機狀態。 在重播過程中,若程式碼嘗試呼叫函式(或執行其他非同步工作),Durable Task Framework 會參考當前編排的執行歷史。 如果發現 活動函式 已經執行並產生結果,就會重播該函式的結果,並讓編排器程式碼繼續執行。 重播會持續,直到函式程式碼完成或排程新的非同步工作為止。
備註
為了讓重播模式正確且可靠地運作,編排器函式程式碼必須具備 確定性。 非確定性編排器程式碼可能導致執行時錯誤或其他意外行為。 欲了解更多關於編排器函式程式碼限制的資訊,請參見編排器函式程式碼限制。
備註
若編排器函式發出日誌訊息,重播行為可能導致重複日誌訊息的發出。 關於此行為發生的原因及如何應對,請參見 應用程式日誌。
協調流程歷程記錄
持久任務框架的事件溯源行為與您撰寫的協調器函式程式碼緊密結合。 假設你有一個活動鏈化的編排器函式,就像接下來的編排器函式一樣。
備註
Azure Functions 的 Node.js 程式設計模型第 4 版已正式推出。 v4 模型旨在為 JavaScript 與 TypeScript 開發者提供更靈活且直覺的使用體驗。 關於 v3 與 v4 差異的更多資訊,請參閱 遷移指南。
在以下程式碼片段中,JavaScript(PM4)代表程式設計模型 v4,即新體驗。
[FunctionName("HelloCities")]
public static async Task<List<string>> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var outputs = new List<string>();
outputs.Add(await context.CallActivityAsync<string>("SayHello", "Tokyo"));
outputs.Add(await context.CallActivityAsync<string>("SayHello", "Seattle"));
outputs.Add(await context.CallActivityAsync<string>("SayHello", "London"));
// Return ["Hello Tokyo!", "Hello Seattle!", "Hello London!"].
return outputs;
}
每當活動函式被排程時,Durable Task Framework 會在不同檢查點儲存該函式的執行狀態。 在每個檢查點,框架會將狀態儲存到一個持久儲存後端,預設是 Azure Table Storage。 此狀態稱為 配器歷史。
記錄資料表
一般而言,持久工作架構會在每個檢查點執行下列動作:
- 將執行歷史存入耐用儲存。
- 將協調器要呼叫的函式訊息排入佇列。
- 排隊訊息以供編排器本身使用,例如「持久計時器」訊息。
當檢查點完成後,編排器功能就可以從記憶體中移除,直到有更多工作要做。
備註
Azure Storage 在儲存資料時,並沒有提供任何關於資料儲存與隊列之間一致性的交易保證。 為了處理失敗,Durable Functions Azure 存儲體提供者會使用最終一致性模式。 這些模式有助於確保在檢查點中途發生當機或連線中斷時,資料不會遺失。 替代的儲存提供者,如 Durable Functions Microsoft SQL Server (MSSQL) 儲存提供者,可能提供更強的一致性保證。
當先前所示函式結束時,其歷史看起來大致與下表中資料在資料表儲存中相似。 為方便說明,條目略作簡略。
| PartitionKey(InstanceId) | 事件類型 | 時間戳 | Input | 名稱 | Result | 地位 |
|---|---|---|---|---|---|---|
| EAEE885B | 執行已開始 | 2021-05-05T18:45:28.852Z | null | HelloCities | ||
| EAEE885B | Orchestrator已啟動 | 2021-05-05T18:45:32.362Z | ||||
| EAEE885B | 任務已排程 | 2021-05-05T18:45:32.670Z | 打個招呼 | |||
| EAEE885B | OrchestratorCompleted | 2021-05-05T18:45:32.670Z | ||||
| EAEE885B | TaskCompleted | 2021-05-05T18:45:34.201Z | “”“”你好東京!“” | |||
| EAEE885B | Orchestrator已啟動 | 2021-05-05T18:45:34.232Z | ||||
| EAEE885B | 任務已排程 | 2021-05-05T18:45:34.435Z | 打個招呼 | |||
| EAEE885B | OrchestratorCompleted | 2021-05-05T18:45:34.435Z | ||||
| EAEE885B | TaskCompleted | 2021-05-05T18:45:34.763Z | “”“你好西雅圖!”” | |||
| EAEE885B | Orchestrator已啟動 | 2021-05-05T18:45:34.857Z | ||||
| EAEE885B | 任務已排程 | 2021-05-05T18:45:34.857Z | 打個招呼 | |||
| EAEE885B | OrchestratorCompleted | 2021-05-05T18:45:34.857Z | ||||
| EAEE885B | TaskCompleted | 2021-05-05T18:45:34.919Z | “”“”你好倫敦!” | |||
| EAEE885B | Orchestrator已啟動 | 2021-05-05T18:45:35.032Z | ||||
| EAEE885B | OrchestratorCompleted | 2021-05-05T18:45:35.044Z | ||||
| EAEE885B | 執行已完成 | 2021-05-05T18:45:35.044Z | “[”“你好東京!”,“”你好西雅圖!“,”“你好倫敦!”“]” | 完成 |
表格欄位包含以下數值:
- PartitionKey:協調的實例 ID。
- EventType:事件類型。 欲了解所有歷史事件類型的詳細說明,請參閱 持久任務框架歷史事件。
- 時間戳記:歷史事件的協調世界時時間戳記。
- 輸入:函數的 JSON 格式輸入。
- 名稱:被呼叫函式的名稱。
- 結果:函式的輸出,具體來說,是其回傳值。
警告
此表格可作為除錯工具。 但請記得,隨著 Durable Functions 擴充的演進,其格式和內容可能會有所改變。
每當在等候工作完成之後繼續函式時,長期工作架構便會從頭重新執行協調器函式。 每次重執行時,它會查詢執行歷史以判斷目前非同步任務是否完成。 如果執行歷史顯示該任務已經完成,框架會重播該任務的輸出,然後繼續執行下一個任務。 此程序會持續進行,直到重播整個執行歷程為止。 一旦當前執行歷史被重播,當地變數就會回復到先前的值。
特點和模式
以下章節將描述編排器功能的特性與模式。
子協調流程
協調器函式可以呼叫活動函式,也可以呼叫其他協調器函式。 例如,您可以從協調器函式庫建置較大的協調流程。 或者,也可以平行執行協調器函式的多個執行個體。
欲了解更多資訊及範例,請參閱持久函式中的子編排(Azure Functions)。
耐用的計時器
編排可以排程 持久計時器 以實現延遲或設定非同步動作的逾時處理。 在 orchestrator 函式中使用持久計時器,取代語言原生 sleep API。
欲了解更多資訊及範例,請參閱「耐用函數計時器」(Azure Functions)。
外部活動
協調器函式可以等候外部事件來更新協調流程實例。 此耐用功能通常用於處理人機互動或其他外部回調。
欲了解更多資訊及範例,請參閱「 處理持久函式中的外部事件(Azure Functions)」。
錯誤處理
Orchestrator 函式可以使用程式設計語言的錯誤處理功能。 既有的模式try/catch支援於編排程式碼中。
Orchestrator 函數也可以將重試策略新增到它們所呼叫的活動或子協調器函數中。 如果活動或子協調器函式失敗並發生例外狀況,指定的重試原則可以自動延遲並重試執行,最多可達到指定的次數。
備註
如果 orchestrator 函式中有未處理的例外,編排實例會結束於一個 Failed 狀態。 協調實例失敗後無法重試。
欲了解更多資訊及範例,請參閱 「持久函式處理錯誤(Azure Functions)」。
重要區段 (Durable Functions 2.x,目前僅限 .NET)
編排實例是單執行緒,因此在編排 中 不會擔心競賽條件。 不過,當協調流程與外部系統互動時,可能會發生競爭情況。 為了在與外部系統互動時減少競賽狀況,編排器函式可透過 .NET 中的方法定義LockAsync。
下列範例程式碼顯示定義重要區段的協調器函式。 它使用該 LockAsync 方法進入臨界區段。 此方法需要傳遞一個或多個對持久實體的參考,這些實體會持續管理鎖定狀態。 此協調流程的單一實例一次只能執行重要區段中的程式碼。
[FunctionName("Synchronize")]
public static async Task Synchronize(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var lockId = new EntityId("LockEntity", "MyLockIdentifier");
using (await context.LockAsync(lockId))
{
// Critical section. Only one orchestration can enter at a time.
}
}
該 LockAsync 方法會取得耐用鎖,並在釋放時返回 IDisposable,從而結束關鍵區域。 此 IDisposable 結果可以與區塊一起使用 using ,以取得關鍵區段的語法表示。 當協調器函式進入重要區段時,只有一個執行個體可以執行該程式碼區塊。 任何試圖進入關鍵區段的其他實例都會被阻擋,直到前一個實例離開該區。
重要區段特性對於協調持久實體的變更也很有用。 欲了解更多關鍵區段資訊,請參見 實體協調。
備註
Durable Functions 2.0 中提供關鍵區段。 目前,只有 .NET 的流程中編排實作此功能。 實體和關鍵區段尚未在 Durable Functions for .NET 孤立工作者編排中提供。
呼叫 HTTP 端點(Durable Functions 2.x)
編排器函式不允許執行 I/O 操作,這在 編排器函式程式碼限制中有所描述。 解決此限制的典型方法是將任何需要執行 I/O 操作的程式碼包裝成活動函式。 與外部系統互動的編排經常使用活動函式來進行 HTTP 呼叫並將結果回傳給編排。
為了簡化這種常見模式,編排器功能可以使用此 CallHttpAsync 方法直接呼叫 HTTP API。
[FunctionName("CheckSiteAvailable")]
public static async Task CheckSiteAvailable(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
Uri url = context.GetInput<Uri>();
// Make an HTTP GET request to the specified endpoint.
DurableHttpResponse response =
await context.CallHttpAsync(HttpMethod.Get, url);
if ((int)response.StatusCode == 400)
{
// Handle error codes.
}
}
除了支援基本的請求/回應模式外,該方法還支援自動處理常見的非同步 HTTP 202 輪詢模式。 它也支援透過 管理身份與外部服務進行認證。
欲了解更多資訊及詳細範例,請參閱 HTTP 特性。
備註
在 Durable Functions 2.0 及之後版本中,可以直接從編排器函式呼叫 HTTP 端點。
多重參數
無法直接將多個參數傳遞至活動函式。 建議傳入物件陣列或組合物件。
在 .NET 中,你也可以使用 ValueTuple 物件來傳遞多個參數。 以下範例使用了 C# 7 加入的 ValueTuple 功能:
[FunctionName("GetCourseRecommendations")]
public static async Task<object> RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
string major = "ComputerScience";
int universityYear = context.GetInput<int>();
object courseRecommendations = await context.CallActivityAsync<object>(
"CourseRecommendations",
(major, universityYear));
return courseRecommendations;
}
[FunctionName("CourseRecommendations")]
public static async Task<object> Mapper([ActivityTrigger] IDurableActivityContext inputs)
{
// Parse the input for the student's major and year in university.
(string Major, int UniversityYear) studentInfo = inputs.GetInput<(string, int)>();
// Retrieve and return course recommendations by major and university year.
return new
{
major = studentInfo.Major,
universityYear = studentInfo.UniversityYear,
recommendedCourses = new []
{
"Introduction to .NET Programming",
"Introduction to Linux",
"Becoming an Entrepreneur"
}
};
}