SqlClient の構成可能な再試行ロジックのコア API
適用対象: .NET Framework .NET .NET Standard
組み込みの再試行ロジック プロバイダーではニーズに対応できない場合、独自のカスタム プロバイダーを作成できます。 その後、これらのプロバイダーを SqlConnection
または SqlCommand
オブジェクトに割り当てて、カスタム ロジックを適用できます。
組み込みプロバイダーは、カスタム プロバイダーの実装に使用できる 3 つのインターフェイスを中心に設計されています。 カスタム再試行プロバイダーは、SqlConnection または SqlCommand の内部再試行プロバイダーと同じ方法で使用できます。
- SqlRetryIntervalBaseEnumerator: 時間間隔のシーケンスを生成します。
- SqlRetryLogicBase: 再試行回数を超えておらず、一時的な条件が満たされた場合に、指定された列挙子の次の時間間隔を取得します。
- SqlRetryLogicBaseProvider: 接続およびコマンド操作に再試行ロジックを適用します。
注意事項
カスタムの再試行ロジック プロバイダーを実装することにより、コンカレンシー、パフォーマンス、例外管理など、すべての側面を管理できます。
例
このサンプルの実装は、カスタマイズの手順を示すためのものであり、可能な限りシンプルに作成されています。 スレッド セーフ、非同期、コンカレンシーなどの高度な手法は含まれていません。 実際の実装の詳細については、 Microsoft.Data.SqlClient GitHub リポジトリの事前定義済み再試行ロジックを参照してください。
カスタムの構成可能な再試行ロジックのクラスを定義します。
- 列挙子: 時間間隔の固定シーケンスを定義し、許容可能な時間の範囲を 2 分から 4 分に延長します。
public class CustomEnumerator : SqlRetryIntervalBaseEnumerator { // Set the maximum acceptable time to 4 minutes private readonly TimeSpan _maxValue = TimeSpan.FromMinutes(4); public CustomEnumerator(TimeSpan timeInterval, TimeSpan maxTime, TimeSpan minTime) : base(timeInterval, maxTime, minTime) {} // Return fixed time on each request protected override TimeSpan GetNextInterval() { return GapTimeInterval; } // Override the validate method with the new time range validation protected override void Validate(TimeSpan timeInterval, TimeSpan maxTimeInterval, TimeSpan minTimeInterval) { if (minTimeInterval < TimeSpan.Zero || minTimeInterval > _maxValue) { throw new ArgumentOutOfRangeException(nameof(minTimeInterval)); } if (maxTimeInterval < TimeSpan.Zero || maxTimeInterval > _maxValue) { throw new ArgumentOutOfRangeException(nameof(maxTimeInterval)); } if (timeInterval < TimeSpan.Zero || timeInterval > _maxValue) { throw new ArgumentOutOfRangeException(nameof(timeInterval)); } if (maxTimeInterval < minTimeInterval) { throw new ArgumentOutOfRangeException(nameof(minTimeInterval)); } } }
- 再試行ロジック: アクティブなトランザクションの一部ではない任意のコマンドに再試行ロジックを実装します。 再試行回数を 60 回から 20 回に減らします。
public class CustomRetryLogic : SqlRetryLogicBase { // Maximum number of attempts private const int maxAttempts = 20; public CustomRetryLogic(int numberOfTries, SqlRetryIntervalBaseEnumerator enumerator, Predicate<Exception> transientPredicate) { if (!(numberOfTries > 0 && numberOfTries <= maxAttempts)) { // 'numberOfTries' should be between 1 and 20. throw new ArgumentOutOfRangeException(nameof(numberOfTries)); } // Assign parameters to the relevant properties NumberOfTries = numberOfTries; RetryIntervalEnumerator = enumerator; TransientPredicate = transientPredicate; Current = 0; } // Prepare this object for the next round public override void Reset() { Current = 0; RetryIntervalEnumerator.Reset(); } public override bool TryNextInterval(out TimeSpan intervalTime) { intervalTime = TimeSpan.Zero; // First try has occurred before starting the retry process. // Check if retry is still allowed bool result = Current < NumberOfTries - 1; if (result) { // Increase the number of attempts Current++; // It's okay if the RetryIntervalEnumerator gets to the last value before we've reached our maximum number of attempts. // MoveNext() will simply leave the enumerator on the final interval value and we will repeat that for the final attempts. RetryIntervalEnumerator.MoveNext(); // Receive the current time from enumerator intervalTime = RetryIntervalEnumerator.Current; } return result; } }
- プロバイダー:
Retrying
イベント発生させずに同期操作を再試行する再試行プロバイダーを実装します。 既存の SqlException 一時例外エラー番号に TimeoutException を追加します。
public class CustomProvider : SqlRetryLogicBaseProvider { // Preserve the given retryLogic on creation public CustomProvider(SqlRetryLogicBase retryLogic) { RetryLogic = retryLogic; } public override TResult Execute<TResult>(object sender, Func<TResult> function) { // Create a list to save transient exceptions to report later if necessary IList<Exception> exceptions = new List<Exception>(); // Prepare it before reusing RetryLogic.Reset(); // Create an infinite loop to attempt the defined maximum number of tries do { try { // Try to invoke the function return function.Invoke(); } // Catch any type of exception for further investigation catch (Exception e) { // Ask the RetryLogic object if this exception is a transient error if (RetryLogic.TransientPredicate(e)) { // Add the exception to the list of exceptions we've retried on exceptions.Add(e); // Ask the RetryLogic for the next delay time before the next attempt to run the function if (RetryLogic.TryNextInterval(out TimeSpan gapTime)) { Console.WriteLine($"Wait for {gapTime} before next try"); // Wait before next attempt Thread.Sleep(gapTime); } else { // Number of attempts has exceeded the maximum number of tries throw new AggregateException("The number of retries has exceeded the maximum number of attempts.", exceptions); } } else { // If the exception wasn't a transient failure throw the original exception throw; } } } while (true); } public override Task<TResult> ExecuteAsync<TResult>(object sender, Func<Task<TResult>> function, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } public override Task ExecuteAsync(object sender, Func<Task> function, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } }
定義されたカスタム型で構成される再試行プロバイダー インスタンスを作成します。
public static SqlRetryLogicBaseProvider CreateCustomProvider(SqlRetryLogicOption options) { // 1. create an enumerator instance CustomEnumerator customEnumerator = new CustomEnumerator(options.DeltaTime, options.MaxTimeInterval, options.MinTimeInterval); // 2. Use the enumerator object to create a new RetryLogic instance CustomRetryLogic customRetryLogic = new CustomRetryLogic(5, customEnumerator, (e) => TransientErrorsCondition(e, options.TransientErrors)); // 3. Create a provider using the RetryLogic object CustomProvider customProvider = new CustomProvider(customRetryLogic); return customProvider; }
- 次の関数は、指定された再試行可能な例外のリストと特別な TimeoutException 例外を使用して例外を評価し、再試行可能かどうかを判断します。
// Return true if the exception is a transient fault. private static bool TransientErrorsCondition(Exception e, IEnumerable<int> retriableConditions) { bool result = false; // Assess only SqlExceptions if (retriableConditions != null && e is SqlException ex) { foreach (SqlError item in ex.Errors) { // Check each error number to see if it is a retriable error number if (retriableConditions.Contains(item.Number)) { result = true; break; } } } // Other types of exceptions can also be assessed else if (e is TimeoutException) { result = true; } return result; }
カスタマイズされた再試行ロジックを使用します。
- 再試行ロジック パラメーターを定義します。
// Define the retry logic parameters var options = new SqlRetryLogicOption() { // Tries 5 times before throwing an exception NumberOfTries = 5, // Preferred gap time to delay before retry DeltaTime = TimeSpan.FromSeconds(1), // Maximum gap time for each delay time before retry MaxTimeInterval = TimeSpan.FromSeconds(20), // SqlException retriable error numbers TransientErrors = new int[] { 4060, 1024, 1025} };
- カスタムの再試行プロバイダーを作成します。
// Create a custom retry logic provider SqlRetryLogicBaseProvider provider = CustomRetry.CreateCustomProvider(options);
- 再試行プロバイダーを SqlConnection.RetryLogicProvider または SqlCommand.RetryLogicProvider に割り当てます。
// Assumes that connection is a valid SqlConnection object // Set the retry logic provider on the connection instance connection.RetryLogicProvider = provider; // Establishing the connection will trigger retry if one of the given transient failure occurs. connection.Open();
Note
構成可能な再試行ロジックを使用する前に、そのスイッチを忘れずに有効にしてください。 詳細については、「構成可能な再試行ロジックを有効にする」を参照してください。