Обработка ошибок в устойчивых функциях (Функции Azure)
Мақала
Оркестрации службы "Устойчивые функции" реализованы в коде и могут использовать встроенные функции обработки ошибок для языка программирования. При добавлении в оркестрации обработки и компенсации ошибок не требуется изучать новые концепции. Однако следует помнить о некоторых особенностях поведения.
Примечание
Общедоступна версия 4 модели программирования Node.js для Функции Azure. Новая модель версии 4 предназначена для более гибкого и интуитивно понятного интерфейса для разработчиков JavaScript и TypeScript. Дополнительные сведения о различиях между версиями 3 и 4 см. в руководстве по миграции.
В следующих фрагментах кода JavaScript (PM4) обозначает новую модель программирования версии 4.
Ошибки в функциях действий
Любое исключение, возникающее в функции действия, маршалируется обратно в функцию оркестратора и выдается как FunctionFailedException. Вы можете написать код для обработки и компенсации ошибок, соответствующий вашим потребностям в функции оркестратора.
Например, рассмотрим следующую функцию оркестратора, которая перемещает средства с одного счета на другой:
[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
});
}
}
Примечание
Предыдущие примеры C# предназначены для расширения "Устойчивые функции" версии 2.x. Для расширения "Устойчивые функции" версии 1.x необходимо использовать DurableOrchestrationContext вместо IDurableOrchestrationContext. Дополнительные сведения о различиях между версиями см. в статье Версии устойчивых функций.
[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,
});
}
});
По умолчанию командлеты в PowerShell не выдают исключений, которые можно перехватить с помощью блоков try/catch. Такое поведение можно реализовать двумя способами:
Использовать флаг -ErrorAction Stop при вызове командлетов, таких как Invoke-DurableActivity.
Задать для переменной предпочтения $ErrorActionPreference значение "Stop" в функции оркестратора перед вызовом командлетов.
Дополнительные сведения об обработке ошибок в PowerShell см. в разделе Try-Catch-Finally документации PowerShell.
@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();
}
}
Если первый вызов функции CreditAccount не удается выполнить, функция оркестратора выполняет компенсацию путем зачисления денежных средств обратно на исходный счет.
Автоматическое повторение попыток при сбое
При вызове функций действий или функций суборкестрации можно указать автоматическую политику повтора. В следующем примере предпринимается попытка вызова функции до трех раз с ожиданием в течение 5 секунд между попытками:
[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);
// ...
}
Примечание
Предыдущие примеры C# предназначены для расширения "Устойчивые функции" версии 2.x. Для расширения "Устойчивые функции" версии 1.x необходимо использовать DurableOrchestrationContext вместо IDurableOrchestrationContext. Дополнительные сведения о различиях между версиями см. в статье Версии устойчивых функций.
@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();
// ...
}
В вызове функции действия в предыдущем примере предусмотрен параметр для настройки политики автоматизации повторов. Существует несколько параметров настройки политики автоматического повтора.
Max number of attempts (Максимальное число попыток): максимальное число попыток. Если задано значение 1, повторная попытка не выполняется.
First retry interval (Интервал до первого повтора): время ожидания перед первой повторной попыткой.
Backoff coefficient (Коэффициент отсрочки): коэффициент, позволяющий определить степень увеличения отсрочки. По умолчанию равен 1.
Max retry interval (Максимальный интервал повтора): максимальное время ожидания между повторными попытками.
Retry timeout (Время ожидания повтора): максимальное время, отведенное на выполнение повторных попыток. Поведение по умолчанию — бесконечное повторение.
Пользовательские обработчики повторных попыток
При использовании .NET или Java вы также можете реализовать обработчики повторных попыток в коде. Это полезно, если декларативных политик повторных попыток недостаточно. Для языков, которые не поддерживают пользовательские обработчики повторных попыток, вы по-прежнему можете реализовать политики повторных попыток с помощью циклов, обработки исключений и таймеров для добавления задержек между повторными попытками.
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 в настоящее время не поддерживает пользовательские обработчики повторных попыток. Однако вы по-прежнему можете реализовать логику повторных попыток непосредственно в функции оркестратора с помощью циклов, обработки исключений и таймеров для добавления задержек между повторными попытками.
JavaScript в настоящее время не поддерживает пользовательские обработчики повторных попыток. Однако вы по-прежнему можете реализовать логику повторных попыток непосредственно в функции оркестратора с помощью циклов, обработки исключений и таймеров для добавления задержек между повторными попытками.
Python в настоящее время не поддерживает пользовательские обработчики повторных попыток. Однако вы по-прежнему можете реализовать логику повторных попыток непосредственно в функции оркестратора с помощью циклов, обработки исключений и таймеров для добавления задержек между повторными попытками.
PowerShell в настоящее время не поддерживает пользовательские обработчики повторных попыток. Однако вы по-прежнему можете реализовать логику повторных попыток непосредственно в функции оркестратора с помощью циклов, обработки исключений и таймеров для добавления задержек между повторными попытками.
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...
}
Время ожидания функций
Можно отменить вызов функции в функции оркестратора, если его завершение занимает слишком много времени. Правильный способ сделать это в настоящее время — создать устойчивый таймер, используя селектор задач "any", как показано в следующем примере:
Предыдущие примеры C# предназначены для расширения "Устойчивые функции" версии 2.x. Для расширения "Устойчивые функции" версии 1.x необходимо использовать DurableOrchestrationContext вместо IDurableOrchestrationContext. Дополнительные сведения о различиях между версиями см. в статье Версии устойчивых функций.
Этот механизм фактически не прекращает текущее выполнение функции действия. Вместо этого он просто позволяет функции оркестратора игнорировать результат и двигаться дальше. Дополнительные сведения см. в документации по таймерам.
необработанных исключений.
Если функция оркестратора завершается сбоем с необработанным исключением, сведения об этом исключении регистрируются в журнале, и экземпляр завершает работу с состоянием Failed.