Obsługa błędów w rozszerzeniu Durable Functions (Azure Functions)
Artykuł
Orkiestracje funkcji durable 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órych musisz nauczyć się dodawać do aranżacji obsługę błędów i rekompensatę. Istnieje jednak kilka zachowań, o których należy pamiętać.
Uwaga
Wersja 4 modelu programowania Node.js dla usługi 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ęzyków JavaScript i TypeScript. Dowiedz się więcej o różnicach między wersjami 3 i v4 w przewodniku migracji.
W poniższych fragmentach kodu 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 marshaling z powrotem do funkcji orkiestratora i zgłaszany jako FunctionFailedException. Możesz napisać kod obsługi błędów i kompensacji, który odpowiada Twoim potrzebom w funkcji orkiestratora.
Rozważmy na przykład następującą funkcję orkiestratora, która przekazuje 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ą rozszerzenia Durable Functions 2.x. W przypadku rozszerzenia Durable Functions 1.x należy użyć funkcji DurableOrchestrationContextIDurableOrchestrationContextzamiast . Aby uzyskać więcej informacji na temat różnic między wersjami, zobacz artykuł Wersje rozszerzenia 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ą preferencji $ErrorActionPreference na "Stop" w funkcji orkiestratora przed wywołaniem poleceń cmdlet.
@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 kompensuje środki z powrotem do konta źródłowego.
Automatyczne ponawianie próby po awarii
Podczas wywoływania funkcji działań lub funkcji aranżacji podrzędnej można określić zasady automatycznego ponawiania. Poniższy przykład próbuje wywołać funkcję maksymalnie trzy razy i czeka 5 sekund między kolejnymi 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ą rozszerzenia Durable Functions 2.x. W przypadku rozszerzenia Durable Functions 1.x należy użyć funkcji DurableOrchestrationContextIDurableOrchestrationContextzamiast . Aby uzyskać więcej informacji na temat różnic między wersjami, zobacz artykuł Wersje rozszerzenia 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 do konfigurowania zasad automatycznego ponawiania. Istnieje kilka opcji dostosowywania zasad automatycznego ponawiania prób:
Maksymalna liczba prób: maksymalna liczba prób. Jeśli zostanie ustawiona 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 ponownymi próbami.
Limit czasu ponawiania prób: maksymalna ilość czasu na ponawianie prób. Domyślne zachowanie polega na ponawianiu próby przez czas nieokreślony.
Niestandardowe programy obsługi ponawiania prób
W przypadku korzystania z platformy .NET lub Języka Java możesz również zaimplementować programy obsługi ponawiania prób w kodzie. Jest to przydatne, gdy zasady ponawiania deklaratywnego nie są wystarczająco wyraziste. 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 w celu 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. Nadal jednak istnieje możliwość zaimplementowania logiki ponawiania prób bezpośrednio w funkcji orkiestratora przy użyciu pętli, obsługi wyjątków i czasomierzy w celu wstrzykiwania opóźnień między ponownymi próbami.
Język JavaScript nie obsługuje obecnie niestandardowych procedur obsługi ponawiania prób. Nadal jednak istnieje możliwość zaimplementowania logiki ponawiania prób bezpośrednio w funkcji orkiestratora przy użyciu pętli, obsługi wyjątków i czasomierzy w celu wstrzykiwania opóźnień między ponownymi próbami.
Język Python nie obsługuje obecnie niestandardowych procedur obsługi ponawiania prób. Nadal jednak istnieje możliwość zaimplementowania logiki ponawiania prób bezpośrednio w funkcji orkiestratora przy użyciu pętli, obsługi wyjątków i czasomierzy w celu wstrzykiwania opóźnień między ponownymi próbami.
Program PowerShell nie obsługuje obecnie niestandardowych procedur obsługi ponawiania prób. Nadal jednak istnieje możliwość zaimplementowania logiki ponawiania prób bezpośrednio w funkcji orkiestratora przy użyciu pętli, obsługi wyjątków i czasomierzy w celu 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 działania funkcji trwa zbyt długo, możesz porzucić wywołanie funkcji w funkcji orkiestratora. 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ą rozszerzenia Durable Functions 2.x. W przypadku rozszerzenia Durable Functions 1.x należy użyć funkcji DurableOrchestrationContextIDurableOrchestrationContextzamiast . Aby uzyskać więcej informacji na temat różnic między wersjami, zobacz artykuł Wersje rozszerzenia Durable Functions.
Ten mechanizm nie kończy wykonywania funkcji działania w toku. Zamiast tego po prostu umożliwia funkcji orkiestrator ignorowanie wyniku i przechodzenie 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 są rejestrowane, a wystąpienie kończy się stanem Failed .