Алгоритм увеличения / уменьшения нагрузки в приложении, основанный на количестве исключений

#algorithm #distributed-system

Вопрос:

У меня куча сообщений, поступающих из очереди. Теперь я хочу динамически изменять % сообщений, которые читаются и обрабатываются моим приложением ( назовем это %трафика). Параметры, по которым я изменяю свой трафик в%, — это количество сообщений, которые не удалось обработать ( ошибки ) моему приложению ( потребителю очереди).

Если я жестко закодирую что-то вроде: «x ошибок за y минут (y можно исправить), сократите трафик до z%». Теперь после этого трафик становится низким, ошибки также становятся низкими. Нужен алгоритм, который учитывает текущий % трафика, количество ошибок и определяет новый %трафика. Диапазон % трафика составляет 25% — 100%

Комментарии:

1. У вас есть какие-либо идеи о том, как может выглядеть этот алгоритм, или какие-либо наброски?

2. Из любопытства, происходит ли сбой обработки сообщений из-за тайм-аутов в дальнейшей цепочке?

Ответ №1:

Вы принимаете значение, обратное проценту сообщений с ошибками, к общему количеству сообщений за определенный промежуток времени, а затем приспосабливаете этот процент к своему диапазону трафика. Таким образом, если вы получите все ошибки, ваш процент трафика составит 25%, а если вы не получите ошибок, ваш процент трафика составит 100%.

 // traffic% 25%
minTraffic = 0.25
// traffic% 100%
maxTraffic = 1.00
// 25% -> 100% is a usable range of 75%
deltaTraffic = maxTraffic - minTraffic
// use Max(total, 1) to avoid divide by zero
error = (erroredMessagesPerTimeFrame / Math.max(totalMessagesPerTimeFrame, 1))
// inverse: error=1.00 becomes 0, error=0.00 becomes 1
invError = 1 - pcError
// linear clamp invError to [minTraffic, maxTraffic]
traffic = minTraffic   (deltaTraffic * invError)
 

Это самая простая реализация, использующая линейную подгонку.

Альтернативная версия может соответствовать вашему значению «invError» для «deltaTraffic», используя вместо этого кривую, это будет весить более высокие и более низкие значения ближе (или дальше) к вашим «minTraffic» и «maxTraffic» в зависимости от того, какой тип кривой вы используете.

Другой альтернативой было бы просто использовать функцию шага

 If "invError" < 50% Then "minTraffic" 
Else If "invError" < 75% Then "minTraffic"   (("maxTraffic" - "minTraffic") / 2)
Else "maxTraffic"
 

Ответ №2:

То, о чем вы просите, называется схемой проектирования автоматического выключателя. Вы можете найти хорошую информацию повсюду; некоторые лучшие результаты поиска находятся здесь, здесь и здесь.

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

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


Вот реализация государственной машины корпорацией Майкрософт. Они (и другие источники) предлагают вам сделать универсальный адаптер, чтобы обернуть ваш код и разделить проблемы.

 ...
    if (IsOpen)
    {
      // The circuit breaker is Open. Check if the Open timeout has expired.
      // If it has, set the state to HalfOpen. Another approach might be to
      // check for the HalfOpen state that had be set by some other operation.
      if (stateStore.LastStateChangedDateUtc   OpenToHalfOpenWaitTime < DateTime.UtcNow)
      {
        // The Open timeout has expired. Allow one operation to execute. Note that, in
        // this example, the circuit breaker is set to HalfOpen after being
        // in the Open state for some period of time. An alternative would be to set
        // this using some other approach such as a timer, test method, manually, and
        // so on, and check the state here to determine how to handle execution
        // of the action.
        // Limit the number of threads to be executed when the breaker is HalfOpen.
        // An alternative would be to use a more complex approach to determine which
        // threads or how many are allowed to execute, or to execute a simple test
        // method instead.
        bool lockTaken = false;
        try
        {
          Monitor.TryEnter(halfOpenSyncObject, ref lockTaken);
          if (lockTaken)
          {
            // Set the circuit breaker state to HalfOpen.
            stateStore.HalfOpen();

            // Attempt the operation.
            action();

            // If this action succeeds, reset the state and allow other operations.
            // In reality, instead of immediately returning to the Closed state, a counter
            // here would record the number of successful operations and return the
            // circuit breaker to the Closed state only after a specified number succeed.
            this.stateStore.Reset();
            return;
          }
        }
        catch (Exception ex)
        {
          // If there's still an exception, trip the breaker again immediately.
          this.stateStore.Trip(ex);

          // Throw the exception so that the caller knows which exception occurred.
          throw;
        }
        finally
        {
          if (lockTaken)
          {
            Monitor.Exit(halfOpenSyncObject);
          }
        }
      }
      // The Open timeout hasn't yet expired. Throw a CircuitBreakerOpen exception to
      // inform the caller that the call was not actually attempted,
      // and return the most recent exception received.
      throw new CircuitBreakerOpenException(stateStore.LastException);
    }
    ...