Obsługa błędów w Durable Functions (Azure Functions)
Artykuł
Orkiestracje funkcji Durable Function są implementowane w kodzie i mogą korzystać z wbudowanych funkcji obsługi błędów języka programowania. Naprawdę nie ma żadnych nowych pojęć, które należy nauczyć się dodawać obsługę błędów i rekompensatę do aranżacji. Istnieje jednak kilka zachowań, o których należy pamiętać.
Uwaga
Wersja 4 modelu programowania Node.js dla Azure Functions jest ogólnie dostępna. Nowy model w wersji 4 został zaprojektowany z myślą o bardziej elastycznym i intuicyjnym środowisku dla deweloperów języka JavaScript i Języka TypeScript. Dowiedz się więcej o różnicach między wersjami 3 i v4 w przewodniku migracji.
W poniższych fragmentach kodu język JavaScript (PM4) oznacza model programowania w wersji 4— nowe środowisko.
Błędy w funkcjach działań
Każdy wyjątek zgłaszany w funkcji działania jest wywoływany z powrotem do funkcji orkiestratora i zgłaszany jako FunctionFailedException. Możesz napisać kod obsługi błędów i rekompensaty, który odpowiada Twoim potrzebom w funkcji orkiestratora.
Rozważmy na przykład następującą funkcję orkiestratora, która transferuje fundusze z jednego konta do innego:
[FunctionName("TransferFunds")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var transferDetails = context.GetInput<TransferOperation>();
await context.CallActivityAsync("DebitAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
try
{
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.DestinationAccount,
Amount = transferDetails.Amount
});
}
catch (Exception)
{
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
}
}
Uwaga
Poprzednie przykłady języka C# dotyczą Durable Functions 2.x. W przypadku Durable Functions 1.x należy użyć DurableOrchestrationContext polecenia zamiast IDurableOrchestrationContext. Aby uzyskać więcej informacji na temat różnic między wersjami, zobacz artykuł dotyczący wersji Durable Functions.
[FunctionName("TransferFunds")]
public static async Task Run(
[OrchestrationTrigger] TaskOrchestrationContext context, TransferOperation transferDetails)
{
await context.CallActivityAsync("DebitAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
try
{
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.DestinationAccount,
Amount = transferDetails.Amount
});
}
catch (Exception)
{
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
}
}
const df = require("durable-functions");
module.exports = df.orchestrator(function* (context) {
const transferDetails = context.df.getInput();
yield context.df.callActivity("DebitAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
try {
yield context.df.callActivity("CreditAccount", {
account: transferDetails.destinationAccount,
amount: transferDetails.amount,
});
} catch (error) {
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
yield context.df.callActivity("CreditAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
}
})
const df = require("durable-functions");
df.app.orchestration("transferFunds", function* (context) {
const transferDetails = context.df.getInput();
yield context.df.callActivity("debitAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
try {
yield context.df.callActivity("creditAccount", {
account: transferDetails.destinationAccount,
amount: transferDetails.amount,
});
} catch (error) {
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
yield context.df.callActivity("creditAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
}
});
Domyślnie polecenia cmdlet w programie PowerShell nie zgłaszają wyjątków, które można przechwycić przy użyciu bloków try/catch. Dostępne są dwie opcje zmiany tego zachowania:
Użyj flagi -ErrorAction Stop podczas wywoływania poleceń cmdlet, takich jak Invoke-DurableActivity.
Ustaw zmienną $ErrorActionPreference preferencji na "Stop" w funkcji orkiestratora przed wywołaniem poleceń cmdlet.
Aby uzyskać więcej informacji na temat obsługi błędów w programie PowerShell, zobacz dokumentację programu PowerShell Try-Catch-Finally .
@FunctionName("TransferFunds")
public void transferFunds(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
TransferOperation transfer = ctx.getInput(TransferOperation.class);
ctx.callActivity(
"DebitAccount",
new OperationArgs(transfer.sourceAccount, transfer.amount)).await();
try {
ctx.callActivity(
"CreditAccount",
new OperationArgs(transfer.destinationAccount, transfer.amount)).await();
} catch (TaskFailedException ex) {
// Refund the source account on failure
ctx.callActivity(
"CreditAccount",
new OperationArgs(transfer.sourceAccount, transfer.amount)).await();
}
}
Jeśli pierwsze wywołanie funkcji CreditAccount zakończy się niepowodzeniem, funkcja orkiestratora skompensuje środki z powrotem na konto źródłowe.
Automatyczne ponawianie próby po awarii
Podczas wywoływania funkcji działań lub funkcji orkiestracji podrzędnej można określić zasady automatycznego ponawiania. Poniższy przykład próbuje wywołać funkcję do trzech razy i czeka 5 sekund między poszczególnymi ponownymi próbami:
[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: 3);
await context.CallActivityWithRetryAsync("FlakyFunction", retryOptions, null);
// ...
}
Uwaga
Poprzednie przykłady języka C# dotyczą Durable Functions 2.x. W przypadku Durable Functions 1.x należy użyć DurableOrchestrationContext polecenia zamiast IDurableOrchestrationContext. Aby uzyskać więcej informacji na temat różnic między wersjami, zobacz artykuł dotyczący wersji Durable Functions.
@FunctionName("TimerOrchestratorWithRetry")
public void timerOrchestratorWithRetry(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
final int maxAttempts = 3;
final Duration firstRetryInterval = Duration.ofSeconds(5);
RetryPolicy policy = new RetryPolicy(maxAttempts, firstRetryInterval);
TaskOptions options = new TaskOptions(policy);
ctx.callActivity("FlakeyFunction", options).await();
// ...
}
Wywołanie funkcji działania w poprzednim przykładzie przyjmuje parametr konfigurowania zasad automatycznego ponawiania prób. Istnieje kilka opcji dostosowywania zasad automatycznego ponawiania prób:
Maksymalna liczba prób: maksymalna liczba prób. Jeśli ustawiono wartość 1, nie będzie ponawiania próby.
Interwał ponawiania prób: czas oczekiwania przed pierwszą próbą ponawiania próby.
Współczynnik wycofywania: współczynnik używany do określania współczynnika wzrostu wycofywania. Wartość domyślna to 1.
Maksymalny interwał ponawiania prób: maksymalny czas oczekiwania między próbami ponawiania próby.
Limit czasu ponawiania prób: maksymalna ilość czasu na ponawianie prób. Domyślne zachowanie polega na ponowieniu próby przez czas nieokreślony.
Niestandardowe programy obsługi ponawiania prób
W przypadku korzystania z platformy .NET lub Java możesz również zaimplementować programy obsługi ponawiania prób w kodzie. Jest to przydatne, gdy deklaratywne zasady ponawiania nie są wystarczająco wyraźne. W przypadku języków, które nie obsługują niestandardowych procedur obsługi ponawiania prób, nadal istnieje możliwość implementowania zasad ponawiania przy użyciu pętli, obsługi wyjątków i czasomierzy do wstrzykiwania opóźnień między ponownymi próbami.
RetryOptions retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: int.MaxValue)
{
Handle = exception =>
{
// True to handle and try again, false to not handle and throw.
if (exception is TaskFailedException failure)
{
// Exceptions from TaskActivities are always this type. Inspect the
// inner Exception to get more details.
}
return false;
};
}
await ctx.CallActivityWithRetryAsync("FlakeyActivity", retryOptions, null);
TaskOptions retryOptions = TaskOptions.FromRetryHandler(retryContext =>
{
// Don't retry anything that derives from ApplicationException
if (retryContext.LastFailure.IsCausedBy<ApplicationException>())
{
return false;
}
// Quit after N attempts
return retryContext.LastAttemptNumber < 3;
});
try
{
await ctx.CallActivityAsync("FlakeyActivity", options: retryOptions);
}
catch (TaskFailedException)
{
// Case when the retry handler returns false...
}
Język JavaScript nie obsługuje obecnie niestandardowych procedur obsługi ponawiania prób. Jednak nadal istnieje możliwość implementacji logiki ponawiania bezpośrednio w funkcji orkiestratora przy użyciu pętli, obsługi wyjątków i czasomierzy do wstrzykiwania opóźnień między ponownymi próbami.
Język JavaScript nie obsługuje obecnie niestandardowych procedur obsługi ponawiania prób. Jednak nadal istnieje możliwość implementacji logiki ponawiania bezpośrednio w funkcji orkiestratora przy użyciu pętli, obsługi wyjątków i czasomierzy do wstrzykiwania opóźnień między ponownymi próbami.
Język Python nie obsługuje obecnie niestandardowych procedur obsługi ponawiania prób. Jednak nadal istnieje możliwość implementacji logiki ponawiania bezpośrednio w funkcji orkiestratora przy użyciu pętli, obsługi wyjątków i czasomierzy do wstrzykiwania opóźnień między ponownymi próbami.
Program PowerShell nie obsługuje obecnie niestandardowych procedur obsługi ponawiania prób. Jednak nadal istnieje możliwość implementacji logiki ponawiania bezpośrednio w funkcji orkiestratora przy użyciu pętli, obsługi wyjątków i czasomierzy do wstrzykiwania opóźnień między ponownymi próbami.
RetryHandler retryHandler = retryCtx -> {
// Don't retry anything that derives from RuntimeException
if (retryCtx.getLastFailure().isCausedBy(RuntimeException.class)) {
return false;
}
// Quit after N attempts
return retryCtx.getLastAttemptNumber() < 3;
};
TaskOptions options = new TaskOptions(retryHandler);
try {
ctx.callActivity("FlakeyActivity", options).await();
} catch (TaskFailedException ex) {
// Case when the retry handler returns false...
}
Limity czasu funkcji
Jeśli ukończenie wywołania funkcji w funkcji orkiestratora trwa zbyt długo. Właściwym sposobem, aby to zrobić dzisiaj, jest utworzenie trwałego czasomierza z selektorem zadań "any", jak w poniższym przykładzie:
Poprzednie przykłady języka C# dotyczą Durable Functions 2.x. W przypadku Durable Functions 1.x należy użyć DurableOrchestrationContext polecenia zamiast IDurableOrchestrationContext. Aby uzyskać więcej informacji na temat różnic między wersjami, zobacz artykuł dotyczący wersji Durable Functions.
Ten mechanizm nie kończy wykonywania funkcji działania w toku. Zamiast tego wystarczy, że funkcja orkiestratora zignoruje wynik i przejdzie dalej. Aby uzyskać więcej informacji, zobacz dokumentację czasomierzy .
Nieobsługiwane wyjątki
Jeśli funkcja orkiestratora zakończy się niepowodzeniem z nieobsługiwanym wyjątkiem, szczegóły wyjątku zostaną zarejestrowane, a wystąpienie zakończy się stanem Failed .