Lidar com erros no Durable Functions (Funções do Azure)
Artigo
As orquestrações da Durable Function são implementadas em código e podem utilizar as funcionalidades incorporadas de processamento de erros da linguagem de programação. Não existem novos conceitos que precise de aprender a adicionar processamento de erros e compensação às suas orquestrações. No entanto, existem alguns comportamentos que deve ter em atenção.
Nota
A versão 4 do modelo de programação Node.js para Funções do Azure está geralmente disponível. O novo modelo v4 foi concebido para ter uma experiência mais flexível e intuitiva para programadores JavaScript e TypeScript. Saiba mais sobre as diferenças entre v3 e v4 no guia de migração.
Nos fragmentos de código seguintes, o JavaScript (PM4) indica o modelo de programação V4, a nova experiência.
Erros nas funções de atividade
Qualquer exceção que seja emitida numa função de atividade é devolvida à função do orquestrador e lançada como um FunctionFailedException. Pode escrever o processamento de erros e o código de compensação que se adequa às suas necessidades na função orchestrator.
Por exemplo, considere a seguinte função de orquestrador que transfere fundos de uma conta para outra:
[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
});
}
}
Nota
Os exemplos anteriores de C# destinam-se a Durable Functions 2.x. Para Durable Functions 1.x, tem de utilizar DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, consulte o artigo Durable Functions versões.
[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,
});
}
});
Por predefinição, os cmdlets no PowerShell não geram exceções que podem ser apanhadas com blocos try/catch. Tem duas opções para alterar este comportamento:
Utilize o sinalizador ao -ErrorAction Stop invocar cmdlets, como Invoke-DurableActivity.
Defina a $ErrorActionPreference variável de preferência como "Stop" na função orchestrator antes de invocar cmdlets.
Para obter mais informações sobre o processamento de erros no PowerShell, veja a documentação do 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();
}
}
Se a primeira chamada da função CreditAccount falhar, a função orchestrator compensa ao creditar os fundos novamente na conta de origem.
Repetição automática de falhas
Quando chama funções de atividade ou funções de sub-orquestração, pode especificar uma política de repetição automática. O exemplo seguinte tenta chamar uma função até três vezes e aguarda 5 segundos entre cada repetição:
[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);
// ...
}
Nota
Os exemplos anteriores de C# destinam-se a Durable Functions 2.x. Para Durable Functions 1.x, tem de utilizar DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, consulte o artigo Durable Functions versões.
@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();
// ...
}
A chamada da função de atividade no exemplo anterior utiliza um parâmetro para configurar uma política de repetição automática. Existem várias opções para personalizar a política de repetição automática:
Número máximo de tentativas: o número máximo de tentativas. Se estiver definido como 1, não haverá repetição.
Primeiro intervalo de repetição: a quantidade de tempo a aguardar antes da primeira tentativa de repetição.
Coeficiente de backoff: o coeficiente utilizado para determinar a taxa de aumento do backoff. A predefinição é 1.
Intervalo máximo de repetição: a quantidade máxima de tempo a aguardar entre tentativas de repetição.
Tempo limite de repetição: a quantidade máxima de tempo a passar a fazer repetições. O comportamento predefinido é tentar novamente indefinidamente.
Processadores de repetição personalizados
Ao utilizar o .NET ou Java, também tem a opção de implementar processadores de repetição no código. Isto é útil quando as políticas declarativas de repetição não são suficientemente expressivas. Para idiomas que não suportam processadores de repetição personalizados, ainda tem a opção de implementar políticas de repetição com ciclos, processamento de exceções e temporizadores para injetar atrasos entre repetições.
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...
}
Atualmente, o JavaScript não suporta processadores de repetição personalizados. No entanto, ainda tem a opção de implementar a lógica de repetição diretamente na função do orquestrador através de ciclos, processamento de exceções e temporizadores para injetar atrasos entre repetições.
Atualmente, o JavaScript não suporta processadores de repetição personalizados. No entanto, ainda tem a opção de implementar a lógica de repetição diretamente na função do orquestrador através de ciclos, processamento de exceções e temporizadores para injetar atrasos entre repetições.
Atualmente, o Python não suporta processadores de repetição personalizados. No entanto, ainda tem a opção de implementar a lógica de repetição diretamente na função do orquestrador através de ciclos, processamento de exceções e temporizadores para injetar atrasos entre repetições.
Atualmente, o PowerShell não suporta processadores de repetição personalizados. No entanto, ainda tem a opção de implementar a lógica de repetição diretamente na função do orquestrador através de ciclos, processamento de exceções e temporizadores para injetar atrasos entre repetições.
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...
}
Tempos limite da função
Poderá querer abandonar uma chamada de função dentro de uma função de orquestrador se estiver a demorar demasiado tempo a concluir. A forma adequada de o fazer atualmente é criar um temporizador durável com um seletor de tarefas "qualquer", como no exemplo seguinte:
Os exemplos anteriores de C# destinam-se a Durable Functions 2.x. Para Durable Functions 1.x, tem de utilizar DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, consulte o artigo Durable Functions versões.
Este mecanismo não termina efetivamente a execução da função de atividade em curso. Em vez disso, permite simplesmente que a função do orquestrador ignore o resultado e avance. Para obter mais informações, veja a documentação temporizadores .
Exceções não processadas
Se uma função do orquestrador falhar com uma exceção não processada, os detalhes da exceção serão registados e a instância será concluída com um Failed estado.