我需要找到在超时的情况下为我的数据库调用添加重试机制的方法,LINQ to SQL用于在我的代码中调用一些sprocs ...
using (MyDataContext dc = new MyDataContext())
{
int result = -1; //denote failure
int count = 0;
while ((result < 0) && (count < MAX_RETRIES))
{
result = dc.myStoredProc1(...);
count++;
}
result = -1;
count = 0;
while ((result < 0) && (count < MAX_RETRIES))
{
result = dc.myStoredProc2(...);
count++;
}
...
...
}
不确定上述代码是否正确或是否带来任何复杂情况。
在MAX_RETRIES到达之后抛出异常会很好,但我不知道如何以及在哪里适当地抛出它们: - )
任何帮助表示赞赏。
答案 0 :(得分:3)
如果您从数据库中获得超时,那么可能它将在几毫秒之后及时响应。
按照你的建议重新进行紧凑循环可能使情况更糟,因为你会给数据库服务器带来不应有的负担,以及在调用代码中绑定一个线程。 在每次重试之间引入等待时间会更安全。
对于更高级的方案,您可以考虑使用渐进式等待模式,在此模式中,如果仍然有超时,则可以在开始时更频繁地重试,然后使用更长和更长的时间间隔。
您可能还想查看本书Release It!中的断路器设计模式,以及该书中描述的许多其他模式和反模式。
State pattern非常适合实施断路器。
答案 1 :(得分:1)
就个人而言,我会在这里使用递归。它使代码更清晰,因为您拥有的唯一“额外代码”是函数的参数。例如:
private MyResult Foo(MyParameters mp, int repeatCall)
{
var result = null;
try
{
result = mp.dc.myStoredProc(...);
}
catch (MyException err)
{
if (repeatCall > 0)
{
result = Foo(mp, repeatCall - 1);
}
else
{
throw;
}
}
return result;
}
我认为这是递归的理想示例。无论调用什么,都不需要关注循环,它可以使代码更清晰。
答案 2 :(得分:0)
正如Mark Seemann所说的那样,使用重试政策来处理超时并不是一个好主意。然而,考虑到延迟,这毕竟是一个好主意。要实现它,您可以使用执行操作方法的custom action invoker,并在发生SQL异常时进行重试。这样,您就不必在每个操作方法的代码中处理重试策略。
我们的系统中没有数据库超时,但我使用相同的技术以通用方式处理sql读取死锁。
答案 3 :(得分:0)
我们使用类似的方法(最好为每次重试实例化一个新的EF上下文):
对不起,但是无法包含SqlExceptionUtil.IsSqlServerErrorType()的代码(过于自定义和多层)。
static public T ExecuteRetryable<T>(
Func<T> function,
out int actualAttempts,
string actionDescriptionForException = "SQL",
int maxTries = 3,
int pauseMaxMillis = 1000,
int pauseMinMillis = 0,
bool alsoPauseBeforeFirstAttempt = false,
bool allowRetryOnTimeout = false)
{
Exception mostRecentException = null;
for (int i = 0; i < maxTries; i++)
{
// Pause at the beginning of the loop rather than end to catch the case when many servers
// start due to inrush of requests (likely). Use a random factor to try and avoid deadlock
// in the first place.
//
if (i > 0 || alsoPauseBeforeFirstAttempt)
Thread.Sleep(new Random
(
// Default Initializer was just based on time, help the random to differ when called at same instant in different threads.
(Int32)((DateTime.Now.Ticks + Thread.CurrentThread.GetHashCode() + Thread.CurrentThread.ManagedThreadId) % Int32.MaxValue)
)
.Next(pauseMinMillis, pauseMaxMillis));
actualAttempts = i + 1;
try
{
T returnValue = function();
return returnValue;
}
catch (Exception ex)
{
// The exception hierarchy may not be consistent so search all inner exceptions.
// Currently it is DbUpdateException -> UpdateException -> SqlException
//
if (!SqlExceptionUtil.IsSqlServerErrorType(ex, SqlServerErrorType.Deadlock) &&
(!allowRetryOnTimeout || !SqlExceptionUtil.IsSqlServerErrorType(ex, SqlServerErrorType.Timeout)))
throw;
mostRecentException = ex;
}
}
throw new Exception(
"Unable to perform action '" + actionDescriptionForException + "' after " + maxTries +
" tries with pauses of [" + pauseMinMillis + "," + pauseMaxMillis + "]ms due to multiple exceptions.",
mostRecentException);
}
用法:
List<SomeTableEntity> result = DatabaseHelpers.ExecuteRetryable<List<SomeTableEntity>>(() =>
{
using (EfCtx ctx = new EfCtx())
{
return ctx.SomeTable.Where(...).ToList()
}
}, out int actualAttempts, allowRetryOnTimeout: true);
如果有人展示如何伪装自定义Linq构造背后的包装代码(如WithRetry(...)),那将是很好的选择。