Dotnet C#에서 SQL 서버 연결을 위한 구성 가능한 재시도 논리

10300 단어
이 샘플의 구현은 단계별 사용자 지정을 보여주기 위해 가능한 한 간단합니다. 스레드 안전, 비동기 및 동시성과 같은 고급 사례는 포함되지 않습니다. 실제 구현에 대해 자세히 알아보려면 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;
    }
}



공급자: 재시도 이벤트 없이 동기 작업을 재시도하는 재시도 공급자를 구현합니다. 기존 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}
};



사용자 지정 재시도 논리를 사용합니다.

재시도 논리 매개변수를 정의합니다.

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



사용하기 전에 구성 가능한 재시도 로직 스위치를 활성화하는 것을 잊지 마십시오. 자세한 내용은 see Enable configurable retry logic .

참조 :

https://docs.microsoft.com/en-us/sql/connect/ado-net/configurable-retry-logic-core-apis-sqlclient?view=sql-server-ver15

좋은 웹페이지 즐겨찾기