首页 > 解决方案 > 瞬态故障重试逻辑最佳实践

问题描述

朋友们,我有一个关于围绕 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 构造之外?

标签: c#transient-failure

解决方案


将我的评论形式化为答案。

重试逻辑是否应该存在于 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));

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


推荐阅读