瞬态故障重试逻辑最佳实践

Transient Fault Retry logic best practices

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

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

should the retry logic live on the outside of the SqlConnection construction?

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

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

您可以在某处定义静态 class 和您的策略列表:

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));

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