Durable Function 业务流程采用代码实现,并可使用编程语言的内置错误处理功能。 实际上,不需要学习任何新概念,就可以将错误处理和补偿添加到业务流程中。 但应注意以下事项。
Nota
适用于 Azure Functions 的 Node.js 编程模型版本 4 处于预览阶段。 新版 v4 模型旨在为 JavaScript 和 TypeScript 开发人员提供更为灵活和直观的体验。 在升级指南中详细了解 v3 和 v4 之间的差异。
在以下代码片段中,JavaScript (PM4) 表示编程模型 V4,即新体验。
活动函数中引发的任何异常都将封送回业务流程协调程序函数,并作为 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
});
}
}
Nota
前面的 C# 示例适用于 Durable Functions 2.x。 对于 Durable Functions 1.x,必须使用 DurableOrchestrationContext
而不是 IDurableOrchestrationContext
。 有关版本之间差异的详细信息,请参阅 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,
});
}
});
import azure.functions as func
import azure.durable_functions as df
def orchestrator_function(context: df.DurableOrchestrationContext):
transfer_details = context.get_input()
yield context.call_activity('DebitAccount', {
'account': transfer_details['sourceAccount'],
'amount' : transfer_details['amount']
})
try:
yield context.call_activity('CreditAccount', {
'account': transfer_details['destinationAccount'],
'amount': transfer_details['amount'],
})
except:
yield context.call_activity('CreditAccount', {
'account': transfer_details['sourceAccount'],
'amount': transfer_details['amount']
})
main = df.Orchestrator.create(orchestrator_function)
默认情况下,PowerShell 中的 cmdlet 不会引发可使用 try/catch 块捕获的异常。 可以采取两种做法来更改此行为:
- 调用 cmdlet 时使用
-ErrorAction Stop
标志,例如 Invoke-DurableActivity
。
- 在调用 cmdlet 之前,将业务流程协调程序函数中的
$ErrorActionPreference
首选项变量设置为 "Stop"
。
param($Context)
$ErrorActionPreference = "Stop"
$transferDetails = $Context.Input
Invoke-DurableActivity -FunctionName 'DebitAccount' -Input @{ account = transferDetails.sourceAccount; amount = transferDetails.amount }
try {
Invoke-DurableActivity -FunctionName 'CreditAccount' -Input @{ account = transferDetails.destinationAccount; amount = transferDetails.amount }
} catch {
Invoke-DurableActivity -FunctionName 'CreditAccount' -Input @{ account = transferDetails.sourceAccount; amount = transferDetails.amount }
}
有关 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);
// ...
}
Nota
前面的 C# 示例适用于 Durable Functions 2.x。 对于 Durable Functions 1.x,必须使用 DurableOrchestrationContext
而不是 IDurableOrchestrationContext
。 有关版本之间差异的详细信息,请参阅 Durable Functions 版本一文。
[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
var options = TaskOptions.FromRetryPolicy(new RetryPolicy(
maxNumberOfAttempts: 3,
firstRetryInterval: TimeSpan.FromSeconds(5)));
await context.CallActivityAsync("FlakyFunction", options: options);
// ...
}
const df = require("durable-functions");
module.exports = df.orchestrator(function*(context) {
const firstRetryIntervalInMilliseconds = 5000;
const maxNumberOfAttempts = 3;
const retryOptions =
new df.RetryOptions(firstRetryIntervalInMilliseconds, maxNumberOfAttempts);
yield context.df.callActivityWithRetry("FlakyFunction", retryOptions);
// ...
});
const df = require("durable-functions");
df.app.orchestration("callActivityWithRetry", function* (context) {
const firstRetryIntervalInMilliseconds = 5000;
const maxNumberOfAttempts = 3;
const retryOptions = new df.RetryOptions(firstRetryIntervalInMilliseconds, maxNumberOfAttempts);
yield context.df.callActivityWithRetry("flakyFunction", retryOptions);
// ...
});
import azure.functions as func
import azure.durable_functions as df
def orchestrator_function(context: df.DurableOrchestrationContext):
first_retry_interval_in_milliseconds = 5000
max_number_of_attempts = 3
retry_options = df.RetryOptions(first_retry_interval_in_milliseconds, max_number_of_attempts)
yield context.call_activity_with_retry('FlakyFunction', retry_options)
main = df.Orchestrator.create(orchestrator_function)
param($Context)
$retryOptions = New-DurableRetryOptions `
-FirstRetryInterval (New-TimeSpan -Seconds 5) `
-MaxNumberOfAttempts 3
Invoke-DurableActivity -FunctionName 'FlakyFunction' -RetryOptions $retryOptions
@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();
// ...
}
上一示例中的活动函数调用使用一个参数来配置自动重试策略。 可通过多种选项自定义自动重试策略:
- 最大尝试次数:尝试的最大次数。 如果设置为 1,则不会重试。
- 首次重试间隔:首次尝试重试前需要等待的时间。
- 回退系数:用来确定回退增加速率的系数。 默认值为 1。
- 最大重试间隔:尝试重试之间需要等待的最长时间。
- 重试超时:执行重试所花费的最长时间。 默认行为是可无限期重试。
使用 .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”任务选择器创建持久计时器,如下例中所示:
[FunctionName("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
TimeSpan timeout = TimeSpan.FromSeconds(30);
DateTime deadline = context.CurrentUtcDateTime.Add(timeout);
using (var cts = new CancellationTokenSource())
{
Task activityTask = context.CallActivityAsync("FlakyFunction");
Task timeoutTask = context.CreateTimer(deadline, cts.Token);
Task winner = await Task.WhenAny(activityTask, timeoutTask);
if (winner == activityTask)
{
// success case
cts.Cancel();
return true;
}
else
{
// timeout case
return false;
}
}
}
Nota
前面的 C# 示例适用于 Durable Functions 2.x。 对于 Durable Functions 1.x,必须使用 DurableOrchestrationContext
而不是 IDurableOrchestrationContext
。 有关版本之间差异的详细信息,请参阅 Durable Functions 版本一文。
[Function("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
TimeSpan timeout = TimeSpan.FromSeconds(30);
DateTime deadline = context.CurrentUtcDateTime.Add(timeout);
using (var cts = new CancellationTokenSource())
{
Task activityTask = context.CallActivityAsync("FlakyFunction");
Task timeoutTask = context.CreateTimer(deadline, cts.Token);
Task winner = await Task.WhenAny(activityTask, timeoutTask);
if (winner == activityTask)
{
// success case
cts.Cancel();
return true;
}
else
{
// timeout case
return false;
}
}
}
const df = require("durable-functions");
const moment = require("moment");
module.exports = df.orchestrator(function*(context) {
const deadline = moment.utc(context.df.currentUtcDateTime).add(30, "s");
const activityTask = context.df.callActivity("FlakyFunction");
const timeoutTask = context.df.createTimer(deadline.toDate());
const winner = yield context.df.Task.any([activityTask, timeoutTask]);
if (winner === activityTask) {
// success case
timeoutTask.cancel();
return true;
} else {
// timeout case
return false;
}
});
const df = require("durable-functions");
const { DateTime } = require("luxon");
df.app.orchestration("timerOrchestrator", function* (context) {
const deadline = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({ seconds: 30 });
const activityTask = context.df.callActivity("flakyFunction");
const timeoutTask = context.df.createTimer(deadline.toJSDate());
const winner = yield context.df.Task.any([activityTask, timeoutTask]);
if (winner === activityTask) {
// success case
timeoutTask.cancel();
return true;
} else {
// timeout case
return false;
}
});
import azure.functions as func
import azure.durable_functions as df
from datetime import datetime, timedelta
def orchestrator_function(context: df.DurableOrchestrationContext):
deadline = context.current_utc_datetime + timedelta(seconds = 30)
activity_task = context.call_activity('FlakyFunction')
timeout_task = context.create_timer(deadline)
winner = yield context.task_any(activity_task, timeout_task)
if winner == activity_task:
timeout_task.cancel()
return True
else:
return False
main = df.Orchestrator.create(orchestrator_function)
param($Context)
$expiryTime = New-TimeSpan -Seconds 30
$activityTask = Invoke-DurableActivity -FunctionName 'FlakyFunction'-NoWait
$timerTask = Start-DurableTimer -Duration $expiryTime -NoWait
$winner = Wait-DurableTask -Task @($activityTask, $timerTask) -NoWait
if ($winner -eq $activityTask) {
Stop-DurableTimerTask -Task $timerTask
return $True
}
else {
return $False
}
@FunctionName("TimerOrchestrator")
public boolean timerOrchestrator(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
Task<Void> activityTask = ctx.callActivity("SlowFunction");
Task<Void> timeoutTask = ctx.createTimer(Duration.ofMinutes(30));
Task<?> winner = ctx.anyOf(activityTask, timeoutTask).await();
if (winner == activityTask) {
// success case
return true;
} else {
// timeout case
return false;
}
}
Nota
此机制实际上不会终止正在进行的活动函数执行。 它只是允许业务流程协调程序函数忽略结果并继续运行。 有关详细信息,请参阅计时器文档。
如果业务流程协调程序函数失败,出现未经处理的异常,则会记录异常的详细信息,且实例的完成状态为 Failed
。