首页 > 解决方案 > 在持久函数中回滚数据库更改

问题描述

假设我有以下编排:

[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 Functions 框架中是否有任何类似的机制?或者我是否需要为每个活动编写回滚实现并在完成每个活动后将更改提交到数据库?

标签: c#azureazure-functionsazure-durable-functions

解决方案


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”的活动)将不会再次执行 - 它的状态将存储在存储中,因此结果将立即可用。运行时在每个活动之后执行一个检查点,因此状态被保留并且它知道它之前完成的位置。

现在要正确处理情况,您必须实现以下算法:

  • 捕获到异常时执行手动回滚
  • 如果失败,将消息推送到例如队列,然后由了解该过程如何工作的人手动处理

虽然最初,它可能看起来像一个很大的缺陷,但实际上,它是一个完美的解决方案 - 确实会发生错误,因此避免瞬态错误(使用重试)总是一个好主意,但如果回滚失败,这清楚地表明存在你的系统有问题。

选择权在于您——您是否具有强一致性并且必须处理可伸缩性问题,或者您使用提供更好可伸缩性但更难使用的松散模型。


推荐阅读