假设我有以下编排:
[FunctionName("Orchestration")]
public static async Task Orchestration_Start([OrchestrationTrigger] DurableOrchestrationContext ctx)
{
await ctx.CallActivityAsync("Foo");
await ctx.CallActivityAsync("Bar");
await Task.WhenAll(ctx.CallActivityAsync("Baz"), ctx.CallActivityAsync("Baz"));
}
我的所有活动都使用Azure SQL数据库,如果任何调用失败,我想撤消之前活动所做的所有更改 - 例如,如果第二次调用Baz
引发异常,我想要撤消所有由Foo
,Bar
,如果第一个Baz
已经完成,我也想撤消其修改。
在非函数应用程序中,我可以将业务流程的整个主体包装在using scope = new TransactionScope()
块中。
这是否适用于潜在的分布式业务流程,如果没有,Azure功能框架中是否有任何类似的机制?或者我是否需要为每个活动编写回滚实现,并在完成每个活动后将更改提交到数据库?
持久功能实现了最终一致性的机制。这是一个完全不同于其他类型的一致性(例如强大)的概念,因为它保证了交易最终会完成。那是什么意思?
通过使用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”的活动)将不会再次执行 - 其状态将存储在存储中,因此结果将立即可用。运行时在每个活动之后执行检查点,因此状态将被保留,并且它知道先前完成的位置。
现在要正确处理一个情况,你必须实现以下算法:
虽然最初,它可能看起来像一个很大的缺陷,事实上,它是一个非常好的解决方案 - 错误确实发生所以避免瞬态(使用重试)总是一个好主意,但如果回滚失败,这清楚地表明存在你的系统有问题。
您可以选择 - 您是否具有很强的一致性并且必须处理可伸缩性问题,或者使用更宽松的模型来提供更好的可伸缩性,但更难以使用。