[英]Polly retry policy with sql holding transaction open
我正在使用 Polly 为暂时的 SQL 错误实施重试策略。 问题是我需要将我的数据库调用包装在一个事务中(因为如果任何一个失败,我想回滚)。 在我从 Polly 实现重试之前,这很容易,因为我只会捕获异常并回滚。 但是,我现在使用下面的代码来实现 Polly 并重试几次。 问题是,当我遇到异常并且 Polly 进行重试并且假设重试不起作用并且所有尝试都失败时,事务保持打开状态并且我收到错误消息“无法在交易”。 我知道为什么会这样,这是因为.WaitAndRetry
将在每次尝试之前执行块中的代码。 这就是我现在回滚的地方。 这适用于除最后一次之外的所有尝试。
问题是,当我有一个事务并且需要在每次失败后回滚时,如何实现 Polly,以便即使在最后一次失败时,它仍然可以回滚?
这是我现在正在做的事情:
return Policy
.Handle<SQLiteException>()
.WaitAndRetry(retryCount: 2, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (exception, retryCount, context) =>
{
connection.Rollback();
Logger.Instance.WriteLog<DataAccess>($"Retry {retryCount} of inserting employee files", LogLevel.Error, exception);
})
.Execute(() =>
{
connection.BeginTransaction();
connection.Update(batch);
connection.Insert(pkgs);
if (pkgStatus != null)
connection.Insert(pkgStatus);
if (extended != null)
connection.Insert(extended);
connection.Commit();
return true;
});
通过一些研究和测试,这是我想出的一种可能的解决方案。 从功能上讲,它可以工作,因此我将其作为答案提供,但我不知道是否有更好的方法来做到这一点,或者 Polly 内部是否有更受支持的方法。
一种方法是将连接事务的内容包装在.Execute
内部,以单独的 try/catch 进行,以便在那里发生回滚,然后重新引发异常,以便 Polly 可以将其拾取并触发重试。
这是我所做的:
return Policy
.Handle<SQLiteException>()
.WaitAndRetry(retryCount: 2, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (exception, retryCount, context) =>
{
//connection.Rollback();
Logger.Instance.WriteLog<DataAccess>($"Retry {retryCount} of inserting employee files", LogLevel.Error, exception);
})
.Execute(() =>
{
try
{
connection.BeginTransaction();
connection.Update(batch);
connection.Insert(pkgs);
if (pkgStatus != null)
connection.Insert(pkgStatus);
if (extended != null)
connection.Insert(extended);
connection.Commit();
return true;
}
catch (SQLiteException sx)
{
connection.Rollback();
throw;
}
});
正如您所描述的WaitAndRetry
的onRetry
委托在重试策略进入睡眠状态之前运行。 该委托通常用于捕获日志信息,而不是执行任何类型的补偿操作。
如果您需要回滚,那么您有几个选择:
NoOp
策略和ExecuteAndCapture
方法Fallback
策略将成功和失败案例分开让我通过一个简化的示例向您展示最后两个:
private static bool isHealthy = true;
static void SampleCall()
{
Console.WriteLine("SampleCall");
isHealthy = false;
throw new NotSupportedException();
}
static void Compensate()
{
Console.WriteLine("Compensate");
isHealthy = true;
}
简而言之:
SampleCall
可以破坏健康的 stateCompensate
。NoOp
+ ExecuteAndCapture
static void Main(string[] args)
{
var retry = Policy<bool>
.HandleResult(isSucceeded => !isSucceeded)
.Retry(2);
var noop = Policy.NoOp();
bool isSuccess = retry.Execute(() =>
{
var result = noop.ExecuteAndCapture(SampleCall);
if (result.Outcome != OutcomeType.Failure)
return true;
Compensate();
return false;
});
Console.WriteLine(isSuccess);
}
NoOp
没有做任何特别的事情。 它将执行提供的委托,仅此而已。ExecuteAndCapture
将执行提供的委托并返回PolicyResult
object,它有几个有用的属性: Outcome
、 FinalException
、 ExceptionType
和Context
Outcome
不是Failure
(因此没有抛出异常),那么我们将返回true
并且不会触发重试策略。OutCome
是Failure
,那么我们将执行Compensate
操作,我们将返回false
以触发重试策略。HandleResult
将检查返回的值并决定是否应该重新执行提供的委托。isSuccess
包含最终结果。
SampleCall
最多执行 3 次(1 次初始调用和 2 次重试)成功,则可能是true
。false
的。Fallback
static void Main(string[] args)
{
var retry = Policy<bool>
.HandleResult(isSucceeded => !isSucceeded)
.Retry(2);
var fallback = Policy<bool>
.Handle<NotSupportedException>()
.Fallback(() => { Compensate(); return false; });
var strategy = Policy.Wrap(retry, fallback);
bool isSuccess = strategy.Execute(() =>
{
SampleCall();
return true;
});
Console.WriteLine(isSuccess);
}
Execute
的委托返回true
。Execute
会将异常传播到执行Compensate
操作的Fallback
策略,然后返回false
以触发重试策略。Policy.Wrap
通常用于定义升级链。 如果内部失败并且没有处理给定的情况,那么它将调用外部。
NotImplementedException
,那么从 Fallback 的角度来看,这是一个未处理的异常,因此需要升级。我希望这两个简单的例子能帮助你决定你喜欢用哪种方式来实现你的目标。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.