Durable Function-orkestreringar implementeras i kod och kan använda programmeringsspråkets inbyggda funktioner för felhantering. Det finns egentligen inga nya begrepp som du behöver lära dig att lägga till felhantering och kompensation i orkestreringarna. Det finns dock några beteenden som du bör känna till.
Kommentar
Version 4 av Node.js programmeringsmodellen för Azure Functions är allmänt tillgänglig. Den nya v4-modellen är utformad för att ha en mer flexibel och intuitiv upplevelse för JavaScript- och TypeScript-utvecklare. Läs mer om skillnaderna mellan v3 och v4 i migreringsguiden.
I följande kodfragment anger JavaScript (PM4) programmeringsmodellen V4, den nya upplevelsen.
Fel i aktivitetsfunktioner
Alla undantag som genereras i en aktivitetsfunktion konverteras tillbaka till orchestrator-funktionen och genereras som en FunctionFailedException. Du kan skriva felhantering och kompensationskod som passar dina behov i orchestrator-funktionen.
Tänk dig till exempel följande orchestrator-funktion som överför medel från ett konto till ett annat:
[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
});
}
}
Kommentar
De tidigare C#-exemplen är för Durable Functions 2.x. För Durable Functions 1.x måste du använda DurableOrchestrationContext i stället för IDurableOrchestrationContext. Mer information om skillnaderna mellan versioner finns i artikeln Durable Functions-versioner .
[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,
});
}
});
Som standard skapar cmdletar i PowerShell inte undantag som kan fångas med hjälp av try/catch-block. Du har två alternativ för att ändra det här beteendet:
-ErrorAction Stop Använd flaggan när du anropar cmdletar, till exempel Invoke-DurableActivity.
Ange inställningsvariabeln $ErrorActionPreference till "Stop" i orchestrator-funktionen innan du anropar cmdletar.
@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();
}
}
Om det första CreditAccount-funktionsanropet misslyckas kompenserar orkestreringsfunktionen genom att kreditera pengarna tillbaka till källkontot.
Automatiskt återförsök vid fel
När du anropar aktivitetsfunktioner eller underorkestreringsfunktioner kan du ange en princip för automatiskt återförsök. Följande exempel försöker anropa en funktion upp till tre gånger och väntar 5 sekunder mellan varje nytt försök:
[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);
// ...
}
Kommentar
De tidigare C#-exemplen är för Durable Functions 2.x. För Durable Functions 1.x måste du använda DurableOrchestrationContext i stället för IDurableOrchestrationContext. Mer information om skillnaderna mellan versioner finns i artikeln Durable Functions-versioner .
@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();
// ...
}
Aktivitetsfunktionsanropet i föregående exempel tar en parameter för att konfigurera en automatisk återförsöksprincip. Det finns flera alternativ för att anpassa principen för automatiskt återförsök:
Maximalt antal försök: Det maximala antalet försök. Om värdet är 1 görs inget nytt försök.
Första återförsöksintervallet: Hur lång tid det tar att vänta innan det första återförsöket.
Backoff-koefficient: Den koefficient som används för att fastställa ökningshastigheten för backoff. Standardvärdet är 1.
Maximalt återförsöksintervall: Den maximala väntetiden mellan återförsök.
Tidsgräns för återförsök: Den maximala tid som du behöver ägna åt att göra återförsök. Standardbeteendet är att försöka igen på obestämd tid.
Anpassade återförsökshanterare
När du använder .NET eller Java kan du också implementera återförsökshanterare i kod. Detta är användbart när deklarativa återförsöksprinciper inte är tillräckligt uttrycksfulla. För språk som inte stöder anpassade återförsökshanterare har du fortfarande möjlighet att implementera återförsöksprinciper med hjälp av loopar, undantagshantering och timers för att mata in fördröjningar mellan återförsök.
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...
}
JavaScript stöder för närvarande inte anpassade återförsökshanterare. Du har dock fortfarande möjlighet att implementera omprövningslogik direkt i orchestrator-funktionen med hjälp av loopar, undantagshantering och timers för att mata in fördröjningar mellan återförsök.
JavaScript stöder för närvarande inte anpassade återförsökshanterare. Du har dock fortfarande möjlighet att implementera omprövningslogik direkt i orchestrator-funktionen med hjälp av loopar, undantagshantering och timers för att mata in fördröjningar mellan återförsök.
Python stöder för närvarande inte anpassade återförsökshanterare. Du har dock fortfarande möjlighet att implementera omprövningslogik direkt i orchestrator-funktionen med hjälp av loopar, undantagshantering och timers för att mata in fördröjningar mellan återförsök.
PowerShell stöder för närvarande inte anpassade återförsökshanterare. Du har dock fortfarande möjlighet att implementera omprövningslogik direkt i orchestrator-funktionen med hjälp av loopar, undantagshantering och timers för att mata in fördröjningar mellan återförsök.
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...
}
Funktionstimeouter
Du kanske vill avbryta ett funktionsanrop i en orkestreringsfunktion om det tar för lång tid att slutföra. Det rätta sättet att göra detta i dag är genom att skapa en varaktig timer med en "valfri" uppgiftsväljare, som i följande exempel:
De tidigare C#-exemplen är för Durable Functions 2.x. För Durable Functions 1.x måste du använda DurableOrchestrationContext i stället för IDurableOrchestrationContext. Mer information om skillnaderna mellan versioner finns i artikeln Durable Functions-versioner .
Den här mekanismen avslutar inte funktionskörningen under pågående aktivitet. I stället tillåter den helt enkelt orkestreringsfunktionen att ignorera resultatet och gå vidare. Mer information finns i dokumentationen om Timers .
Ohanterade undantag
Om en orchestrator-funktion misslyckas med ett ohanterat undantag loggas information om undantaget och instansen slutförs med status Failed .