Durable Functions 实现了一种最终一致性机制。这是一个与其他类型的一致性(例如强一致性)完全不同的概念,因为它保证事务最终将完成。这意味着什么?
通过使用TransactionScope
,您可以确保,如果事务中出现任何问题,将自动执行回滚。在 Durable Function 中,情况并非如此 - 您没有自动功能,可以为您提供这样的功能 - 事实上,如果您的示例中的第二个活动失败,您最终将在数据库中存储不一致的数据。
要在这种情况下实现事务,您必须尝试/捕获可能的问题并执行逻辑,这将允许您减轻错误:
[FunctionName("Orchestration")]
public static async Task Orchestration_Start([OrchestrationTrigger] DurableOrchestrationContext ctx)
{
try
{
await ctx.CallActivityAsync("Foo");
await ctx.CallActivityAsync("Bar");
await Task.WhenAll(ctx.CallActivityAsync("Baz"), ctx.CallActivityAsync("Baz"));
}
catch(Exception)
{
// Do something...
}
}
还可以实施重试策略以避免暂时错误:
public static async Task Run(DurableOrchestrationContext context)
{
var retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: 3);
await ctx.CallActivityWithRetryAsync("FlakyFunction", retryOptions, null);
// ...
}
但是,重要的是要了解 Durable Functions 的运行时如何真正管理出现问题时的情况。让我们假设以下代码失败:
[FunctionName("Orchestration")]
public static async Task Orchestration_Start([OrchestrationTrigger] DurableOrchestrationContext ctx)
{
await ctx.CallActivityAsync("Foo");
await ctx.CallActivityAsync("Bar"); // THROWS!
await Task.WhenAll(ctx.CallActivityAsync("Baz"), ctx.CallActivityAsync("Baz"));
}
如果您重播整个编排,第一个活动(传递“Foo”的活动)将不会再次执行 - 它的状态将存储在存储中,因此结果将立即可用。运行时在每个活动之后执行一个检查点,因此状态被保留并且它知道它之前完成的位置。
现在要正确处理情况,您必须实现以下算法:
- 捕获到异常时执行手动回滚
- 如果失败,将消息推送到例如队列,然后由了解该过程如何工作的人手动处理
虽然最初,它可能看起来像一个很大的缺陷,但实际上,它是一个完美的解决方案 - 确实会发生错误,因此避免瞬态错误(使用重试)总是一个好主意,但如果回滚失败,这清楚地表明存在你的系统有问题。
选择权在于您——您是否具有强一致性并且必须处理可伸缩性问题,或者您使用提供更好可伸缩性但更难使用的松散模型。