【问题标题】:Transient Fault Retry logic best practices瞬态故障重试逻辑最佳实践
【发布时间】:2018-06-02 12:48:06
【问题描述】:

朋友们,我有一个关于在执行 SQL 命令时实施简单重试策略的问题。

我的问题是:重试循环应该封装连接和事务的构造,还是应该存在于连接内部。

例如:

private void RetryLogSave(DynamicParameters parameters, int retries = 3)
{    
    int tries = 0;

    using (var connection = new SqlConnection(_connectionString))
    {
        connection.Open();

        using (var transaction = connection.BeginTransaction())
        {
            var logItemCommand = new CommandDefinition(commandText: Constants.InsertLogItem,
                parameters: parameters, transaction: transaction, commandType: System.Data.CommandType.Text);

            do
            {
                try
                {
                    tries++;
                    connection.Execute(logItemCommand);
                    transaction.Commit();
                    break;
                }
                catch (Exception exc)
                {
                    if (tries == retries)
                    {
                        transaction.Rollback();
                        throw exc;
                    }
                    Task.Delay(100 * tries).Wait();
                }
            }
            while (true);
        }
}
}

我在这里所做的是否适当且可接受,或者重试逻辑是否应该存在于 SqlConnection 构造之外?

【问题讨论】:

  • 我通常为此使用Polly。事务应该在重试块内。尽量保持你的交易/连接尽可能小
  • @DmitryPavliv 我听说过关于 Polly 的好消息,今天可能会尝试一下。所以,你的建议是交易应该在区块内。那么连接实例化呢?另外,这意味着每次重试都会回滚,对吗?
  • 连接应该打开尽可能短的时间。理想情况下,您在 odbc 驱动程序中启用了连接池(这通常是默认设置)。这意味着一旦您关闭连接,它实际上会回到池中,并且另一种方法可以重用它。在重试期间打开连接可能会迫使系统创建过多的物理连接(因为您没有将它们释放回连接池)并耗尽您的 sql server
  • @DmitryPavliv 好的,有道理。

标签: c# transient-failure


【解决方案1】:

将我的 cmets 形式化为答案。

重试逻辑是否应该位于 SqlConnection 外部 施工?

是的。如果在保持连接打开的情况下执行重试逻辑,那么您正在浪费资源。在您等待 N 秒重试时,其他人可能会使用它。打开/关闭连接通常(对于大多数 ODBC 驱动程序)在连接池机制之上实现。您实际上并没有关闭它 - 您允许连接回到池中以供其他人重用。在重试期间保持连接打开将迫使系统创建越来越多的新物理连接(因为它们没有返回到池中),最终您的 SQL Server 将耗尽。

关于重试机制 - 为了不重新发明轮子,我通常使用 Polly 库。

您可以使用您的政策列表在某处定义静态类:

public static class MyPolices
{
    // Retry, waiting a specified duration between each retry
    public static Policy RetryPolicy = Policy
       .Handle<Exception>() // can be more specific like SqlException
       .WaitAndRetry(new[]
       {
          TimeSpan.FromSeconds(1),
          TimeSpan.FromSeconds(2),
          TimeSpan.FromSeconds(3)
       });
}

然后,将您的方法简化为

private void LogSave(DynamicParameters parameters)
{    
    using (var connection = new SqlConnection(_connectionString))
    {
        connection.Open();

        using (var transaction = connection.BeginTransaction())
        {
            // make sure to not forget to dispose your command
            var logItemCommand = new CommandDefinition(commandText: Constants.InsertLogItem,
                parameters: parameters, transaction: transaction, commandType: System.Data.CommandType.Text);

            try
            {
                // not sure if conn.Execute is your extension method?
                connection.Execute(logItemCommand);
                transaction.Commit();
            }
            catch (Exception exc)
            {
                transaction.Rollback();
                throw;
            }
        }
    }
}

这样称呼它

MyPolices.RetryPolicy.Execute(() => LogSave(parameters));

这种方法将使您的代码更加可靠,保持重试逻辑隔离。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-21
    • 2021-11-08
    • 1970-01-01
    • 1970-01-01
    • 2016-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多