瞬态故障重试逻辑最佳实践
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));
这种方法将使您的代码更加可靠,使重试逻辑保持隔离。
朋友们,我有一个关于围绕执行 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));
这种方法将使您的代码更加可靠,使重试逻辑保持隔离。