Шаблон повторовRetry pattern

Позвольте приложению обрабатывать временные сбои при попытке подключения к службе или сетевому ресурсу с помощью повторных попыток выполнения операции, завершившейся сбоем, открытым образом.Enable an application to handle transient failures when it tries to connect to a service or network resource, by transparently retrying a failed operation. Таким образом приложение станет работать стабильнее.This can improve the stability of the application.

Контекст и проблемаContext and problem

Приложение, которое взаимодействует с элементами, запущенными в облаке, должно быть чувствительно к временным сбоям, которые могут случаться в этой среде.An application that communicates with elements running in the cloud has to be sensitive to the transient faults that can occur in this environment. Сбои включают в себя кратковременную потерю сетевого подключения для компонентов и служб, временную недоступность службы или наличие времени ожидания, вызванного занятостью службы.Faults include the momentary loss of network connectivity to components and services, the temporary unavailability of a service, or timeouts that occur when a service is busy.

Эти ошибки часто устраняются без вмешательства со стороны пользователя, поэтому, если повторить действие через некоторый промежуток времени, оно с большой вероятностью будет выполнено успешно.These faults are typically self-correcting, and if the action that triggered a fault is repeated after a suitable delay it's likely to be successful. Например, с помощью службы базы данных, которая обрабатывает большое количество одновременных запросов, можно реализовать стратегию регулирования, временно отклоняющую все последующие запросы до того момента, пока рабочая нагрузка не станет менее интенсивной.For example, a database service that's processing a large number of concurrent requests can implement a throttling strategy that temporarily rejects any further requests until its workload has eased. Попытка подключения приложения к базе данных может завершиться сбоем, но повторная попытка подключения через некоторое время с большой вероятностью будет выполнена успешно.An application trying to access the database might fail to connect, but if it tries again after a delay it might succeed.

РешениеSolution

В облаке временные сбои встречаются часто, поэтому приложение должно быть разработано соответствующим образом, чтобы легко и прозрачно обрабатывать эти сбои.In the cloud, transient faults aren't uncommon and an application should be designed to handle them elegantly and transparently. Это сводит к минимуму воздействие сбоев на бизнес-задачи, которые выполняет приложение.This minimizes the effects faults can have on the business tasks the application is performing.

Если приложение обнаруживает сбой при попытке отправить запрос к удаленной службе, оно может обработать этот сбой с помощью стратегий ниже:If an application detects a failure when it tries to send a request to a remote service, it can handle the failure using the following strategies:

  • Отмена.Cancel. Если ошибка указывает, что сбой является не временным или при повторной попытке его обработка не будет успешной, приложение должно отменить операцию и сформировать отчет об исключении.If the fault indicates that the failure isn't transient or is unlikely to be successful if repeated, the application should cancel the operation and report an exception. Например, сбой аутентификации, вызванный предоставлением неверных учетных данных, не завершится успешно, независимо от количества повторных попыток.For example, an authentication failure caused by providing invalid credentials is not likely to succeed no matter how many times it's attempted.

  • Повтор.Retry. Если определенная ошибка в отчете является нестандартной или редкой, вероятнее всего, она вызвана непредвиденными обстоятельствами, например повреждением сетевого пакета во время его передачи.If the specific fault reported is unusual or rare, it might have been caused by unusual circumstances such as a network packet becoming corrupted while it was being transmitted. В этом случае приложение может попытаться сразу же снова отправить запрос, завершившийся сбоем, так как маловероятно, что этот сбой повторится, и запрос, скорее всего, завершится успешно.In this case, the application could retry the failing request again immediately because the same failure is unlikely to be repeated and the request will probably be successful.

  • Повтор через некоторое время.Retry after delay. Если сбой вызван одной из множества распространенных проблем с подключением или занятостью, сети или службе может потребоваться короткий промежуток на устранение ошибок подключения или удаления списка невыполненных работ.If the fault is caused by one of the more commonplace connectivity or busy failures, the network or service might need a short period while the connectivity issues are corrected or the backlog of work is cleared. По истечении необходимого времени приложение повторно выполнит запрос.The application should wait for a suitable time before retrying the request.

Для более распространенных временных сбоев период между повторными попытками следует выбирать с учетом максимально равномерного распределения запросов из нескольких экземпляров приложения.For the more common transient failures, the period between retries should be chosen to spread requests from multiple instances of the application as evenly as possible. Это снижает вероятность перегрузки занятой службы.This reduces the chance of a busy service continuing to be overloaded. Если несколько экземпляров приложения постоянно перегружают службу запросами повторных попыток, этой службе потребуется больше времени на восстановление.If many instances of an application are continually overwhelming a service with retry requests, it'll take the service longer to recover.

Если запрос по-прежнему завершается сбоем, приложение может ожидать некоторое время, а затем повторит попытку.If the request still fails, the application can wait and make another attempt. При необходимости этот процесс может повторяться с увеличением задержки между повторными попытками, пока не будет выполнено максимальное число запросов.If necessary, this process can be repeated with increasing delays between retry attempts, until some maximum number of requests have been attempted. Задержку можно увеличить последовательно или экспоненциально, в зависимости от типа сбоя и вероятности его устранения в течение этого времени ожидания.The delay can be increased incrementally or exponentially, depending on the type of failure and the probability that it'll be corrected during this time.

На схеме ниже показан вызов операции в размещенной службе по этому шаблону.The following diagram illustrates invoking an operation in a hosted service using this pattern. Если после определенного числа попыток запрос завершается сбоем, приложение должно обработать ошибку как исключение соответствующим образом.If the request is unsuccessful after a predefined number of attempts, the application should treat the fault as an exception and handle it accordingly.

Рис. 1. Вызов операции в размещенной службе с использованием шаблона повторов

Приложение должно перенести все попытки доступа к удаленной службе в код, который реализует политику повторов, соответствующую одной из стратегий выше.The application should wrap all attempts to access a remote service in code that implements a retry policy matching one of the strategies listed above. Запросы, отправленные в разные службы, могут относиться к различным политикам.Requests sent to different services can be subject to different policies. Некоторые поставщики предоставляют библиотеки, которые реализуют политики повторных попыток, где приложение может указывать максимальное число повторных попыток, время между этими попытками и другие параметры.Some vendors provide libraries that implement retry policies, where the application can specify the maximum number of retries, the time between retry attempts, and other parameters.

Приложение должно записывать в журнал подробные сведения об ошибках и невыполненных операциях.An application should log the details of faults and failing operations. Эти сведения полезны для операторов.This information is useful to operators. Если служба часто недоступна или занята, зачастую это происходит из-за того, что служба исчерпала свои ресурсы.If a service is frequently unavailable or busy, it's often because the service has exhausted its resources. Такие ошибки будут возникать реже, если развернуть службу.You can reduce the frequency of these faults by scaling out the service. Например, если служба базы данных постоянно перегружена, полезно разделить базу данных и распределить нагрузку между несколькими серверами.For example, if a database service is continually overloaded, it might be beneficial to partition the database and spread the load across multiple servers.

Microsoft Entity Framework предоставляет инструменты для повторного выполнения операций в базе данных.Microsoft Entity Framework provides facilities for retrying database operations. Большинство служб Azure и клиентских пакетов SDK также содержат механизм повтора.Also, most Azure services and client SDKs include a retry mechanism. Дополнительные сведения см. в статье, посвященной конкретным рекомендациям по использованию механизма повторов.For more information, see Retry guidance for specific services.

Проблемы и рекомендацииIssues and considerations

При выборе схемы реализации этого шаблона следует учитывать следующие моменты.You should consider the following points when deciding how to implement this pattern.

Политика повтора должна быть настроена в соответствии с бизнес-требованиями приложения и типами сбоя.The retry policy should be tuned to match the business requirements of the application and the nature of the failure. Некоторые некритические операции лучше настроить так, чтобы они завершались при первой ошибке, так как выполнение нескольких повторных попыток снижает пропускную способность приложения.For some noncritical operations, it's better to fail fast rather than retry several times and impact the throughput of the application. Например, в интерактивном веб-приложении, которое получает доступ к удаленной службе, лучше завершить сбоем несколько повторных попыток с небольшой задержкой между ними и отобразить соответствующее сообщение для пользователя (например, "Повторите попытку позже").For example, in an interactive web application accessing a remote service, it's better to fail after a smaller number of retries with only a short delay between retry attempts, and display a suitable message to the user (for example, “please try again later”). Для приложения пакета более подходящим вариантом является увеличение количества повторных попыток с экспоненциальным увеличением задержки между ними.For a batch application, it might be more appropriate to increase the number of retry attempts with an exponentially increasing delay between attempts.

Интенсивная политика повтора с минимальной задержкой между попытками и большое число повторных попыток может еще больше снизить производительность максимально занятой службы или службы с нагрузкой, близкой к максимальной.An aggressive retry policy with minimal delay between attempts, and a large number of retries, could further degrade a busy service that's running close to or at capacity. Эта политика повтора также может повлиять на скорость реагирования приложения, если оно непрерывно пытается выполнить операцию, завершившуюся сбоем.This retry policy could also affect the responsiveness of the application if it's continually trying to perform a failing operation.

Если после достаточного количества повторных попыток запрос по-прежнему завершается сбоем, приложению лучше предотвратить отправку запросов к тому же ресурсу и просто сразу сформировать отчет о сбое.If a request still fails after a significant number of retries, it's better for the application to prevent further requests going to the same resource and simply report a failure immediately. По истечении периода ожидания приложение может отправить еще несколько запросов, чтобы проверить, будут ли они успешны.When the period expires, the application can tentatively allow one or more requests through to see whether they're successful. Дополнительные сведения об этой стратегии см. в статье, посвященной шаблону автоматического выключения.For more details of this strategy, see the Circuit Breaker pattern.

Рассмотрим вопрос идемпотентности операции.Consider whether the operation is idempotent. Если она является идемпотентной, выполнение повторной попытки безопасно.If so, it's inherently safe to retry. В противном случае повторные попытки могут вызвать дополнительное количество выполнений операции с непредвиденными побочными эффектами.Otherwise, retries could cause the operation to be executed more than once, with unintended side effects. Например, служба может принять запрос, успешно обработать его, но отправка ответа завершается сбоем.For example, a service might receive the request, process the request successfully, but fail to send a response. На этом этапе логика повторных попыток может повторно отправить запрос, предполагая, что не был получен первый.At that point, the retry logic might re-send the request, assuming that the first request wasn't received.

Запрос к службе может завершиться ошибкой в силу ряда причин, вызывающих различные исключения в зависимости от характера сбоя.A request to a service can fail for a variety of reasons raising different exceptions depending on the nature of the failure. Некоторые исключения указывают на сбой, который можно быстро устранить, в то время как другие указывают, что на устранение сбоя потребуется больше времени.Some exceptions indicate a failure that can be resolved quickly, while others indicate that the failure is longer lasting. Для политики повтора полезно регулировать время между попытками повтора в зависимости от типа исключения.It's useful for the retry policy to adjust the time between retry attempts based on the type of the exception.

Рассмотрим, как повторное выполнение операции, являющейся частью транзакции, повлияет на общую согласованность транзакций.Consider how retrying an operation that's part of a transaction will affect the overall transaction consistency. Настройте соответствующим образом политику повтора для операций транзакций, чтобы увеличить вероятность успеха и устранить необходимость обхода всех шагов выполнения транзакции.Fine tune the retry policy for transactional operations to maximize the chance of success and reduce the need to undo all the transaction steps.

Полностью протестируйте весь код повторных попыток для ряда условий сбоя.Ensure that all retry code is fully tested against a variety of failure conditions. Код не должен значительно влиять на производительность или надежность приложения, вызывать чрезмерную нагрузку на службы или ресурсы или создавать состояния гонки или узкие места.Check that it doesn't severely impact the performance or reliability of the application, cause excessive load on services and resources, or generate race conditions or bottlenecks.

Реализуйте логику повторных попыток только в тех расположениях, в которых полностью предусмотрен контекст операций, которые могут завершиться сбоем.Implement retry logic only where the full context of a failing operation is understood. Например, если задача, которая содержит политику повтора, вызывает другую задачу, которая также содержит эту политику, этот дополнительный уровень повторных попыток может вызвать дополнительную задержку обработки.For example, if a task that contains a retry policy invokes another task that also contains a retry policy, this extra layer of retries can add long delays to the processing. Мы рекомендуем настроить задачу более низкого уровня для завершения работы при первой ошибке и формирования отчета о причине сбоя для задачи, которая его вызвала.It might be better to configure the lower-level task to fail fast and report the reason for the failure back to the task that invoked it. Эта задача более высокого уровня затем может обработать сбой на основе собственной политики.This higher-level task can then handle the failure based on its own policy.

Важно записывать в журнал все сбои подключения, которые вызывают повторные попытки, чтобы определить основные проблемы приложения, служб и ресурсов.It's important to log all connectivity failures that cause a retry so that underlying problems with the application, services, or resources can be identified.

Проанализируйте сбои, которые вероятнее всего могут возникнуть в службе или ресурсе, чтобы понять, являются ли они продолжительными или временными,Investigate the faults that are most likely to occur for a service or a resource to discover if they're likely to be long lasting or terminal. что значит, что в таком случае сбой лучше обработать как исключение.If they are, it's better to handle the fault as an exception. Исключение можно вывести в отчет или журнал, затем приложение может повторить попытку, вызвав другую службу (если она доступна), или продолжать работать в режиме ограниченной функциональности.The application can report or log the exception, and then try to continue either by invoking an alternative service (if one is available), or by offering degraded functionality. Дополнительные сведения о том, как обнаружить и обработать длительные сбои, см. в статье, посвященной шаблону автоматического выключения.For more information on how to detect and handle long-lasting faults, see the Circuit Breaker pattern.

Когда следует использовать этот шаблонWhen to use this pattern

Используйте этот шаблон в случае временных сбоев приложения, так как он взаимодействует с удаленной службой или обращается к удаленному ресурсу.Use this pattern when an application could experience transient faults as it interacts with a remote service or accesses a remote resource. Предполагается, что эти сбои будут непродолжительными, и следующая попытка повтора запроса, завершившегося ранее сбоем, будет успешна.These faults are expected to be short lived, and repeating a request that has previously failed could succeed on a subsequent attempt.

Этот шаблон может оказаться неэффективным в следующих случаях:This pattern might not be useful:

  • Если предполагается длительный сбой, так как он может повлиять на скорость реагирования приложения.When a fault is likely to be long lasting, because this can affect the responsiveness of an application. Приложение может пытаться повторить запрос, который, вероятнее всего, завершится сбоем, напрасно используя время и ресурсы.The application might be wasting time and resources trying to repeat a request that's likely to fail.
  • Для обработки сбоев, вызванных не временными ошибками, например внутренних исключений, вызванных ошибками в бизнес-логике приложения.For handling failures that aren't due to transient faults, such as internal exceptions caused by errors in the business logic of an application.
  • Как альтернатива устранения проблем масштабируемости в системе.As an alternative to addressing scalability issues in a system. Если в приложении часто возникают сбои, связанные с занятостью, вероятнее всего, это говорит о том, что необходимо масштабировать службу или ресурс, к которому направлен запрос.If an application experiences frequent busy faults, it's often a sign that the service or resource being accessed should be scaled up.

ПримерExample

В этом примере на C# показана реализация шаблона повторов.This example in C# illustrates an implementation of the Retry pattern. Метод OperationWithBasicRetryAsync ниже асинхронно вызывает внешнюю службу с помощью метода TransientOperationAsync.The OperationWithBasicRetryAsync method, shown below, invokes an external service asynchronously through the TransientOperationAsync method. Выходные данные метода TransientOperationAsync будут относиться к конкретной службе и исключены из примера кода.The details of the TransientOperationAsync method will be specific to the service and are omitted from the sample code.

private int retryCount = 3;
private readonly TimeSpan delay = TimeSpan.FromSeconds(5);

public async Task OperationWithBasicRetryAsync()
{
  int currentRetry = 0;

  for (;;)
  {
    try
    {
      // Call external service.
      await TransientOperationAsync();

      // Return or break.
      break;
    }
    catch (Exception ex)
    {
      Trace.TraceError("Operation Exception");

      currentRetry++;

      // Check if the exception thrown was a transient exception
      // based on the logic in the error detection strategy.
      // Determine whether to retry the operation, as well as how
      // long to wait, based on the retry strategy.
      if (currentRetry > this.retryCount || !IsTransient(ex))
      {
        // If this isn't a transient error or we shouldn't retry,
        // rethrow the exception.
        throw;
      }
    }

    // Wait to retry the operation.
    // Consider calculating an exponential delay here and
    // using a strategy best suited for the operation and fault.
    await Task.Delay(delay);
  }
}

// Async method that wraps a call to a remote service (details not shown).
private async Task TransientOperationAsync()
{
  ...
}

Инструкция, вызывающая этот метод, содержится в блоке try-catch, выполняющемся с помощью цикла for.The statement that invokes this method is contained in a try/catch block wrapped in a for loop. Цикл for создается при успешном завершении вызова метода TransientOperationAsync без исключения.The for loop exits if the call to the TransientOperationAsync method succeeds without throwing an exception. Если вызов метода TransientOperationAsync завершается сбоем, блок catch анализирует причину сбоя.If the TransientOperationAsync method fails, the catch block examines the reason for the failure. Если предполагается временная ошибка, после небольшой задержки операция выполняется повторно в коде.If it's believed to be a transient error the code waits for a short delay before retrying the operation.

Этот цикл также отслеживает количество повторов выполнения операции. Если три попытки выполнения завершаются сбоем, предполагается длительное исключение.The for loop also tracks the number of times that the operation has been attempted, and if the code fails three times the exception is assumed to be more long lasting. Если исключение не является временным или является длительным, обработчик блока catch выдает исключение.If the exception isn't transient or it's long lasting, the catch handler throws an exception. Это исключение выходит из цикла for, и его перехватывает код, вызывающий метод OperationWithBasicRetryAsync.This exception exits the for loop and should be caught by the code that invokes the OperationWithBasicRetryAsync method.

Метод IsTransient ниже проверяет определенный набор исключений, соответствующих среде, в которой выполняется код.The IsTransient method, shown below, checks for a specific set of exceptions that are relevant to the environment the code is run in. Определение временного исключения будет различаться в зависимости от соответствующих ресурсов и среды, в которой выполняется операция.The definition of a transient exception will vary according to the resources being accessed and the environment the operation is being performed in.

private bool IsTransient(Exception ex)
{
  // Determine if the exception is transient.
  // In some cases this is as simple as checking the exception type, in other
  // cases it might be necessary to inspect other properties of the exception.
  if (ex is OperationTransientException)
    return true;

  var webException = ex as WebException;
  if (webException != null)
  {
    // If the web exception contains one of the following status values
    // it might be transient.
    return new[] {WebExceptionStatus.ConnectionClosed,
                  WebExceptionStatus.Timeout,
                  WebExceptionStatus.RequestCanceled }.
            Contains(webException.Status);
  }

  // Additional exception checking logic goes here.
  return false;
}
  • Шаблон прерывателя.Circuit Breaker pattern. Если предполагается длительный сбой, лучше реализовать шаблон автоматического выключения.If a failure is expected to be more long lasting, it might be more appropriate to implement the Circuit Breaker pattern. Объединение шаблонов повторных попыток и автоматического выключения обеспечивает комплексный подход к обработке ошибок.Combining the Retry and Circuit Breaker patterns provides a comprehensive approach to handling faults.

  • Для большинства служб Azure клиентские пакеты SDK включают встроенную логику повторных попыток.For most Azure services, the client SDKs include built-in retry logic. Дополнительные сведения см. в статье Руководство по повторным попыткам для служб Azure.For more information, see Retry guidance for Azure services.

  • Прежде чем писать пользовательскую логику повторных попыток, рассмотрите возможность использования общей платформы, такой как Polly для .NET или Resilience4j для Java.Before writing custom retry logic, consider using a general framework such as Polly for .NET or Resilience4j for Java.