Fouten in Durable Functions afhandelen (Azure Functions)
Artikel
Durable Function-indelingen worden geïmplementeerd in code en kunnen gebruikmaken van de ingebouwde functies voor foutafhandeling van de programmeertaal. Er zijn echt geen nieuwe concepten die u hoeft te leren om foutafhandeling en compensatie toe te voegen aan uw indelingen. Er zijn echter enkele gedragingen waar u rekening mee moet houden.
Notitie
Versie 4 van het Node.js programmeermodel voor Azure Functions is algemeen beschikbaar. Het nieuwe v4-model is ontworpen voor een flexibelere en intuïtievere ervaring voor JavaScript- en TypeScript-ontwikkelaars. Meer informatie over de verschillen tussen v3 en v4 vindt u in de migratiehandleiding.
In de volgende codefragmenten geeft JavaScript (PM4) het programmeermodel V4 aan, de nieuwe ervaring.
Fouten in activiteitsfuncties
Elke uitzondering die wordt gegenereerd in een activiteitsfunctie, wordt teruggezet naar de orchestratorfunctie en gegenereerd als een FunctionFailedException. U kunt foutafhandelings- en compensatiecode schrijven die aan uw behoeften voldoet in de orchestrator-functie.
Denk bijvoorbeeld aan de volgende orchestratorfunctie waarmee geld van de ene rekening naar de andere wordt overboekt:
[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
});
}
}
Notitie
De vorige C#-voorbeelden zijn voor Durable Functions 2.x. Voor Durable Functions 1.x moet u gebruiken DurableOrchestrationContext in plaats van IDurableOrchestrationContext. Zie het artikel Durable Functions versies voor meer informatie over de verschillen tussen versies.
[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,
});
}
});
Cmdlets in PowerShell genereren standaard geen uitzonderingen die kunnen worden onderschept met try/catch-blokken. U hebt twee opties om dit gedrag te wijzigen:
Gebruik de -ErrorAction Stop vlag bij het aanroepen van cmdlets, zoals Invoke-DurableActivity.
Stel de $ErrorActionPreference voorkeursvariabele in op "Stop" in de orchestratorfunctie voordat u cmdlets aanroept.
@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();
}
}
Als de eerste aanroep van de functie CreditAccount mislukt, compenseert de orchestrator-functie door het geld terug te schrijven naar de bronrekening.
Automatisch opnieuw proberen bij fout
Wanneer u activiteitsfuncties of subindelingsfuncties aanroept, kunt u een beleid voor automatisch opnieuw proberen opgeven. In het volgende voorbeeld wordt geprobeerd een functie maximaal drie keer aan te roepen en wordt er 5 seconden gewacht tussen elke nieuwe poging:
[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);
// ...
}
Notitie
De vorige C#-voorbeelden zijn voor Durable Functions 2.x. Voor Durable Functions 1.x moet u gebruiken DurableOrchestrationContext in plaats van IDurableOrchestrationContext. Zie het artikel Durable Functions versies voor meer informatie over de verschillen tussen versies.
@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();
// ...
}
De aanroep van de activiteitsfunctie in het vorige voorbeeld gebruikt een parameter voor het configureren van een beleid voor automatisch opnieuw proberen. Er zijn verschillende opties voor het aanpassen van het beleid voor automatisch opnieuw proberen:
Maximum aantal pogingen: het maximum aantal pogingen. Als dit is ingesteld op 1, wordt er geen nieuwe poging uitgevoerd.
Interval voor eerste nieuwe poging: de hoeveelheid tijd die moet worden gewacht voordat de eerste poging opnieuw wordt geprobeerd.
Uitstelcoëfficiënt: de coëfficiënt die wordt gebruikt om de snelheid van toename van uitstel te bepalen. Standaardwaarde is 1.
Maximuminterval voor opnieuw proberen: de maximale hoeveelheid tijd om te wachten tussen nieuwe pogingen.
Time-out voor opnieuw proberen: de maximale hoeveelheid tijd die u kunt besteden aan het uitvoeren van nieuwe pogingen. Het standaardgedrag is om het voor onbepaalde tijd opnieuw te proberen.
Aangepaste handlers voor opnieuw proberen
Wanneer u .NET of Java gebruikt, hebt u ook de mogelijkheid om handlers voor opnieuw proberen in code te implementeren. Dit is handig wanneer beleid voor declaratieve nieuwe pogingen niet expressief genoeg is. Voor talen die geen ondersteuning bieden voor aangepaste handlers voor opnieuw proberen, hebt u nog steeds de mogelijkheid om beleid voor opnieuw proberen te implementeren met behulp van lussen, uitzonderingsafhandeling en timers voor het injecteren van vertragingen tussen nieuwe pogingen.
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 biedt momenteel geen ondersteuning voor aangepaste handlers voor opnieuw proberen. U hebt echter nog steeds de mogelijkheid om logica voor opnieuw proberen rechtstreeks in de orchestrator-functie te implementeren met behulp van lussen, uitzonderingsafhandeling en timers voor het injecteren van vertragingen tussen nieuwe pogingen.
JavaScript biedt momenteel geen ondersteuning voor aangepaste handlers voor opnieuw proberen. U hebt echter nog steeds de mogelijkheid om logica voor opnieuw proberen rechtstreeks in de orchestrator-functie te implementeren met behulp van lussen, uitzonderingsafhandeling en timers voor het injecteren van vertragingen tussen nieuwe pogingen.
Python biedt momenteel geen ondersteuning voor aangepaste handlers voor opnieuw proberen. U hebt echter nog steeds de mogelijkheid om logica voor opnieuw proberen rechtstreeks in de orchestrator-functie te implementeren met behulp van lussen, uitzonderingsafhandeling en timers voor het injecteren van vertragingen tussen nieuwe pogingen.
PowerShell biedt momenteel geen ondersteuning voor aangepaste handlers voor opnieuw proberen. U hebt echter nog steeds de mogelijkheid om logica voor opnieuw proberen rechtstreeks in de orchestrator-functie te implementeren met behulp van lussen, uitzonderingsafhandeling en timers voor het injecteren van vertragingen tussen nieuwe pogingen.
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...
}
Functietime-outs
U kunt een functieaanroep binnen een orchestratorfunctie afbreken als het te lang duurt om deze te voltooien. De juiste manier om dit vandaag te doen, is door een duurzame timer te maken met een taakkiezer 'elke', zoals in het volgende voorbeeld:
De vorige C#-voorbeelden zijn voor Durable Functions 2.x. Voor Durable Functions 1.x moet u gebruiken DurableOrchestrationContext in plaats van IDurableOrchestrationContext. Zie het artikel Durable Functions versies voor meer informatie over de verschillen tussen versies.
Met dit mechanisme wordt de uitvoering van de actieve activiteitsfunctie niet daadwerkelijk beëindigd. In plaats daarvan kan de orchestratorfunctie het resultaat negeren en verdergaan. Zie de documentatie over Timers voor meer informatie.
Onverwerkte uitzonderingen
Als een orchestratorfunctie mislukt met een niet-verwerkte uitzondering, worden de details van de uitzondering geregistreerd en wordt de instantie voltooid met een Failed status.