Тестирование повторных попыток и тайм-аутов в приложениях .NET, использующих Microsoft.Extensions.Http.Resilience

Tip

Впервые сталкиваетесь с ограничением пропускной способности? Узнайте что такое троттлинг и как с ним работать.

На первый взгляд
Цель: Убедитесь, что обработчик устойчивости HTTP .NET повторяет попытки, увеличивает интервал между повторами и что тайм-аут срабатывает, как ожидается.
Время: 15 минут
Подключаемые модули:GenericRandomErrorPlugin, RetryAfterPlugin, LatencyPlugin
Предварительные требования:Настройте Dev Proxy, это приложение .NET, использующее Microsoft.Extensions.Http.Resilience

Вы добавили AddStandardResilienceHandler() к вашему HttpClient. Как узнать, что это работает? API, которые вы вызываете, редко удаётся заставить отказать по запросу, и модульные тесты, имитирующие HttpMessageHandler, пропускают конвейер устойчивости, который требуется протестировать.

Dev Proxy находится между приложением и API. Он возвращает ошибки и медленные ответы вашему приложению, поэтому приложение работает без изменений, а обработчик устойчивости реагирует на реальные HTTP-ответы. Вы увидите каждую попытку в выходных данных прокси-сервера разработки.

Что делает стандартный обработчик отказоустойчивости

Прежде чем тестировать, узнайте, чего ожидать. С параметрами по умолчанию: AddStandardResilienceHandler()

Behavior Default
Повторы включены HTTP 500 и выше, 408, 429, HttpRequestExceptionи TimeoutRejectedException
Количество повторных попыток 3, с экспоненциальным откатом и jitter, начиная с 2 секунд
Заголовок Retry-After Уважаемый. Обработчик ждет столько времени, сколько запрашивает API.
Тайм-аут попытки 10 секунд на попытку
Общий тайм-аут 30 секунд для запроса, включая все повторные попытки

Полный список стратегий и их значений по умолчанию см. в разделе "Стандартный обработчик устойчивости" по умолчанию.

Направьте приложение через Dev Proxy

.NET использует системный прокси-сервер, поэтому при запуске Dev Proxy он перехватывает запросы приложения без изменений кода. Дополнительные сведения см. в разделе "Использование Dev Proxy с приложениями .NET".

Имитировать временные ошибки

Создайте конфигурацию прокси-сервера разработки, которая завершает запросы к вашему API с ошибками, при которых обработчик выполняет повторные попытки. В этом примере используется https://api.contoso.com. Замените его на URL-адрес API, который вызывает ваше приложение.

Файл: devproxyrc.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
  "plugins": [
    {
      "name": "RetryAfterPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll"
    },
    {
      "name": "GenericRandomErrorPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
      "configSection": "transientErrors"
    }
  ],
  "urlsToWatch": [
    "https://api.contoso.com/*"
  ],
  "transientErrors": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.schema.json",
    "errorsFile": "transient-errors.json",
    "rate": 50
  }
}

Caution

Добавьте RetryAfterPlugin перед GenericRandomErrorPlugin в вашем файле конфигурации. Если вы добавите его после, GenericRandomErrorPlugin отклоняет запрос, прежде чем RetryAfterPlugin сможет его проверить.

В файле с ошибками определите ответ при ограничении скорости и две ошибки сервера. Значение @dynamic задает заголовок Retry-After и сообщает RetryAfterPlugin, что нужно проверить, что ваше приложение ждет столько времени, прежде чем снова вызвать API.

Файл: transient-errors.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.errorsfile.schema.json",
  "errors": [
    {
      "request": {
        "url": "https://api.contoso.com/*"
      },
      "responses": [
        {
          "statusCode": 429,
          "headers": [
            {
              "name": "Retry-After",
              "value": "@dynamic"
            }
          ]
        },
        {
          "statusCode": 500
        },
        {
          "statusCode": 503
        }
      ]
    }
  ]
}

Запустите Dev Proxy и запустите приложение.

devproxy --config-file devproxyrc.json

При частоте сбоев в 50% большинство запросов выполняется после одной или двух повторных попыток. В выходных данных прокси-сервера разработки проверьте следующее:

  • После ответа 429 следующая попытка обратиться к тому же URL-адресу выполняется по истечении Retry-After. По умолчанию Dev Proxy использует 5 секунд. Если приложение вызывает API слишком рано, RetryAfterPlugin сообщает об этом и регулирует запрос.
  • Ваше приложение не отправляет больше попыток, чем вы настроили.
  • Запросы, которые вы не хотите повторять, например, запрос POST на создание записи, отправляются только один раз. Чтобы исключить их, вызовите options.Retry.DisableForUnsafeHttpMethods() или options.Retry.DisableFor(...).

Проверьте, что происходит при истечении повторных попыток

Повторные попытки скрывают кратковременные сбои. Кроме того, необходимо знать, что делает ваше приложение, когда API продолжает давать сбои. Запустите прокси-сервер разработки с частотой сбоев 100%:

devproxy --config-file devproxyrc.json --failure-rate 100

Dev Proxy показывает 4 попытки для каждого запроса: исходный запрос и 3 повторные попытки. После последней повторной попытки стандартный обработчик не выбрасывает исключение. Он возвращает вашему коду последний ответ с ошибкой. Проверьте, что ваше приложение делает с ним. Например, EnsureSuccessStatusCode() бросает HttpRequestException, и GetStringAsync() тоже бросает. Убедитесь, что приложение отображает полезное сообщение или использует запасной вариант, вместо аварийного завершения или отображения универсальной ошибки.

Тайм-ауты тестов

Медленные API активируют другой путь в обработчике. Чтобы протестировать его, используйте LatencyPlugin, чтобы задержать ответы дольше, чем на 10 секунд ожидания попытки.

Файл: devproxyrc.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
  "plugins": [
    {
      "name": "LatencyPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
      "configSection": "slowApi"
    }
  ],
  "urlsToWatch": [
    "https://api.contoso.com/*"
  ],
  "slowApi": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/latencyplugin.schema.json",
    "minMs": 11000,
    "maxMs": 15000
  }
}

Запустите Dev Proxy и запустите приложение. Каждая попытка занимает более 10 секунд, поэтому время ожидания попытки отменяет её, и обработчик повторяет попытку. Через 30 секунд общее время ожидания отменяет запрос, а ваш код получит TimeoutRejectedException. Убедитесь, что приложение перехватывает это и сообщает пользователю, что произошло.

Tip

Чтобы протестировать те же сценарии с собственными параметрами устойчивости, измените значения в AddStandardResilienceHandler(options => ...) вызове и снова запустите те же конфигурации прокси-сервера разработки.

Следующий шаг

Узнайте больше об имитации ограничения частоты запросов в любом API.

См. также