[C #] Как внедрить логику повторов в LINQ to SQL, чтобы справиться с таймаутами? - PullRequest
5 голосов
/ 07 апреля 2010

Мне нужно найти способы добавить механизм повторения к моим вызовам БД в случае тайм-аута, 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 достигло этого, но я не знаю, как и куда их бросить соответствующим образом: -)

Любая помощь оценена.

Ответы [ 4 ]

3 голосов
/ 07 апреля 2010

Если вы получите тайм-аут из своей базы данных, то не очень вероятно , что он ответит своевременно через несколько миллисекунд.

Повторная попытка в замкнутом цикле, как вы предлагаете, может усугубить плохую ситуацию , поскольку вы бы наложили чрезмерную нагрузку на сервер базы данных, а также связали бы поток в вызывающем коде. Было бы безопаснее ввести время ожидания между каждой повторной попыткой.

Для более сложных сценариев вы могли бы рассмотреть схему прогрессивного ожидания, при которой вы повторяете попытку чаще в начале, а затем с более длительными и более длительными интервалами, если у вас все еще есть тайм-ауты.

Возможно, вы также захотите взглянуть на шаблон проектирования Автоматический выключатель из книги Release It! , а также на многие другие шаблоны и анти-шаблоны, описанные в этой книге. .

Шаблон State хорошо подходит для реализации автоматического выключателя.

1 голос
/ 07 апреля 2010

Лично я бы использовал здесь рекурсию. Это делает код чище, поскольку единственный «дополнительный код», который у вас есть, - это параметр в функции. Например:

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;
}

Думаю, это идеальный пример для рекурсии. Что бы это ни вызывало, это не должно касаться зацикливания, и это делает НАМНОГО более чистый код.

0 голосов
/ 24 августа 2018

Мы используем что-то вроде этого (предпочитаем, чтобы для каждой попытки создавался новый EF Context):

Извините, но код для 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 (...).

0 голосов
/ 26 марта 2012

Как правильно заметил Марк Симанн, нецелесообразно использовать политику повторов для борьбы с таймаутами. Однако, учитывая задержку, это может быть хорошей идеей в конце концов. Для его реализации вы можете использовать invoker пользовательского действия , который выполняет ваш метод действия и заботится о повторных попытках в случае исключения SQL. Таким образом, вам не нужно заботиться о политике повторов в каждом коде метода действия.

У нас нет тайм-аута базы данных в нашей системе, но я использую ту же технику для обработки взаимоблокировок чтения SQL в общем виде.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...