繁体   English   中英

使用 sql 保持交易打开的 Polly 重试策略

[英]Polly retry policy with sql holding transaction open

我正在使用 Polly 为暂时的 SQL 错误实施重试策略。 问题是我需要将我的数据库调用包装在一个事务中(因为如果任何一个失败,我想回滚)。 在我从 Polly 实现重试之前,这很容易,因为我只会捕获异常并回滚。 但是,我现在使用下面的代码来实现 Polly 并重试几次。 问题是,当我遇到异常并且 Polly 进行重试并且假设重试不起作用并且所有尝试都失败时,事务保持打开状态并且我收到错误消息“无法在交易”。 我知道为什么会这样,这是因为.WaitAndRetry将在每次尝试之前执行块中的代码。 这就是我现在回滚的地方。 这适用于除最后一次之外的所有尝试。

问题是,当我有一个事务并且需要在每次失败后回滚时,如何实现 Polly,以便即使在最后一次失败时,它仍然可以回滚?

这是我现在正在做的事情:

return Policy
    .Handle<SQLiteException>()
    .WaitAndRetry(retryCount: 2, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (exception, retryCount, context) =>
    {
        connection.Rollback();
         Logger.Instance.WriteLog<DataAccess>($"Retry {retryCount} of inserting employee files", LogLevel.Error, exception);
    })
    .Execute(() =>
    {
        connection.BeginTransaction();
        connection.Update(batch);
        connection.Insert(pkgs);
        if (pkgStatus != null)
            connection.Insert(pkgStatus);
        if (extended != null)
            connection.Insert(extended);
        connection.Commit();
        return true;
    });

通过一些研究和测试,这是我想出的一种可能的解决方案。 从功能上讲,它可以工作,因此我将其作为答案提供,但我不知道是否有更好的方法来做到这一点,或者 Polly 内部是否有更受支持的方法。

一种方法是将连接事务的内容包装在.Execute内部,以单独的 try/catch 进行,以便在那里发生回滚,然后重新引发异常,以便 Polly 可以将其拾取并触发重试。

这是我所做的:

return Policy
    .Handle<SQLiteException>()
    .WaitAndRetry(retryCount: 2, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (exception, retryCount, context) =>
    {
        //connection.Rollback();
        Logger.Instance.WriteLog<DataAccess>($"Retry {retryCount} of inserting employee files", LogLevel.Error, exception);
    })
    .Execute(() =>
    {
        try
        {
            connection.BeginTransaction();
            connection.Update(batch);
            connection.Insert(pkgs);
            if (pkgStatus != null)
                connection.Insert(pkgStatus);
            if (extended != null)
                connection.Insert(extended);
            connection.Commit();
            return true;
        }
        catch (SQLiteException sx)
        {
            connection.Rollback();
            throw;
        }
    });

正如您所描述的WaitAndRetryonRetry委托在重试策略进入睡眠状态之前运行。 该委托通常用于捕获日志信息,而不是执行任何类型的补偿操作。

如果您需要回滚,那么您有几个选择:

  • 回滚是您要执行的委托的一部分
    • 用策略修饰的方法
  • 通过使用NoOp策略和ExecuteAndCapture方法
  • 使用Fallback策略将成功和失败案例分开

让我通过一个简化的示例向您展示最后两个:

简化应用

private static bool isHealthy = true;
static void SampleCall()
{
    Console.WriteLine("SampleCall");
    isHealthy = false;
    throw new NotSupportedException();
}

static void Compensate()
{
    Console.WriteLine("Compensate");
    isHealthy = true;
}

简而言之:

  • 我们有SampleCall可以破坏健康的 state
  • 我们有可以进行自我修复的Compensate

NoOp + ExecuteAndCapture

static void Main(string[] args)
{
    var retry = Policy<bool>
        .HandleResult(isSucceeded => !isSucceeded)
        .Retry(2);

    var noop = Policy.NoOp();

    bool isSuccess = retry.Execute(() =>
    {
        var result = noop.ExecuteAndCapture(SampleCall);
        if (result.Outcome != OutcomeType.Failure) 
            return true;
        
        Compensate();
        return false;

    });

    Console.WriteLine(isSuccess);
}
  • 顾名思义, NoOp没有做任何特别的事情。 它将执行提供的委托,仅此而已。
  • ExecuteAndCapture将执行提供的委托并返回PolicyResult object,它有几个有用的属性: OutcomeFinalExceptionExceptionTypeContext
    • 如果Outcome不是Failure (因此没有抛出异常),那么我们将返回true并且不会触发重试策略。
    • 如果OutComeFailure ,那么我们将执行Compensate操作,我们将返回false以触发重试策略。
  • HandleResult将检查返回的值并决定是否应该重新执行提供的委托。
  • isSuccess包含最终结果。
    • 如果SampleCall最多执行 3 次(1 次初始调用和 2 次重试)成功,则可能是true
    • 或者,如果所有 3 次执行都失败,则它可能是false的。

Fallback

static void Main(string[] args)
{
    var retry = Policy<bool>
        .HandleResult(isSucceeded => !isSucceeded)
        .Retry(2);

    var fallback = Policy<bool>
        .Handle<NotSupportedException>()
        .Fallback(() => { Compensate(); return false; });

    var strategy = Policy.Wrap(retry, fallback);

    bool isSuccess = strategy.Execute(() =>
    {
        SampleCall();
        return true;
    });

    Console.WriteLine(isSuccess);
}
  • 在这里,我们将成功案例和失败案例分开。
    • 如果成功,我们会从Execute的委托返回true
    • 如果失败, Execute会将异常传播到执行Compensate操作的Fallback策略,然后返回false以触发重试策略。
  • Policy.Wrap通常用于定义升级链。 如果内部失败并且没有处理给定的情况,那么它将调用外部。
    • 例如,如果NotImplementedException ,那么从 Fallback 的角度来看,这是一个未处理的异常,因此需要升级。
  • 在我们的例子中,我们使用它来执行自我修复,然后触发重试。

我希望这两个简单的例子能帮助你决定你喜欢用哪种方式来实现你的目标。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM