Поделиться через


Создание устойчивых HTTP-приложений: ключевые шаблоны разработки

Создание надежных HTTP-приложений, которые могут восстановиться после временных ошибок сбоя, является общим требованием. В этой статье предполагается, что вы уже прочитали общие сведения о устойчивой разработке приложений, так как в этой статье представлены основные понятия. Чтобы помочь в создании устойчивых HTTP-приложений, пакет NuGet Microsoft.Extensions.Http.Resilience предоставляет механизмы устойчивости специально для этого HttpClient. Этот пакет NuGet использует библиотеку Microsoft.Extensions.Resilience и Polly, который является популярным проектом с открытым исходным кодом. Дополнительные сведения см. в разделе Polly.

Начало работы

Чтобы использовать шаблоны устойчивости в HTTP-приложениях, установите пакет NuGet Microsoft.Extensions.Http.Resilience .

dotnet add package Microsoft.Extensions.Http.Resilience --version 8.0.0

Дополнительные сведения см. в разделе dotnet add package or Manage package dependencies in .NET applications.

Добавление устойчивости к HTTP-клиенту

Чтобы добавить устойчивость к HttpClientобъекту, необходимо создать цепочку вызовов IHttpClientBuilder типа, возвращаемого при вызове любого из доступных AddHttpClient методов. Дополнительные сведения см. в разделе IHttpClientFactory с .NET.

Доступно несколько расширений, ориентированных на устойчивость. Некоторые из них являются стандартными, поэтому используют различные отраслевые рекомендации, а другие — более настраиваемыми. При добавлении устойчивости следует добавить только один обработчик устойчивости и избежать стека обработчиков. Если необходимо добавить несколько обработчиков устойчивости, следует рассмотреть возможность использования AddResilienceHandler метода расширения, что позволяет настроить стратегии устойчивости.

Внимание

Все примеры, приведенные в этой статье, зависят от AddHttpClient API из библиотеки Microsoft.Extensions.Http , которая возвращает IHttpClientBuilder экземпляр. Экземпляр IHttpClientBuilder используется для настройки HttpClient и добавления обработчика устойчивости.

Добавление стандартного обработчика устойчивости

Стандартный обработчик устойчивости использует несколько стратегий устойчивости, размещенных на вершине друг друга, с параметрами по умолчанию для отправки запросов и обработки временных ошибок. Стандартный обработчик устойчивости добавляется путем вызова AddStandardResilienceHandler метода расширения в экземпляре IHttpClientBuilder .

var services = new ServiceCollection();

var httpClientBuilder = services.AddHttpClient<ExampleClient>(
    configureClient: static client =>
    {
        client.BaseAddress = new("https://jsonplaceholder.typicode.com");
    });

Предыдущий код:

  • Создает экземпляр ServiceCollection.
  • HttpClient Добавляет тип в ExampleClient контейнер службы.
  • Настраивает используемый HttpClient в "https://jsonplaceholder.typicode.com" качестве базового адреса.
  • Создает этот объект httpClientBuilder , используемый в других примерах в этой статье.

Более реальный пример будет зависеть от размещения, например, описанного в статье универсального узла .NET. Используя пакет NuGet Microsoft.Extensions.Hosting, рассмотрим следующий обновленный пример:

using Http.Resilience.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

IHttpClientBuilder httpClientBuilder = builder.Services.AddHttpClient<ExampleClient>(
    configureClient: static client =>
    {
        client.BaseAddress = new("https://jsonplaceholder.typicode.com");
    });

Предыдущий код аналогичен подходу к созданию вручную ServiceCollection , но вместо этого используется Host.CreateApplicationBuilder() для создания узла, предоставляющего службы.

Определяется ExampleClient следующим образом:

using System.Net.Http.Json;

namespace Http.Resilience.Example;

/// <summary>
/// An example client service, that relies on the <see cref="HttpClient"/> instance.
/// </summary>
/// <param name="client">The given <see cref="HttpClient"/> instance.</param>
internal sealed class ExampleClient(HttpClient client)
{
    /// <summary>
    /// Returns an <see cref="IAsyncEnumerable{T}"/> of <see cref="Comment"/>s.
    /// </summary>
    public IAsyncEnumerable<Comment?> GetCommentsAsync()
    {
        return client.GetFromJsonAsAsyncEnumerable<Comment>("/comments");
    }
}

Предыдущий код:

  • Определяет ExampleClient тип, имеющий конструктор, который принимает объект HttpClient.
  • Предоставляет GetCommentsAsync метод, который отправляет запрос /comments GET в конечную точку и возвращает ответ.

Тип Comment определяется следующим образом:

namespace Http.Resilience.Example;

public record class Comment(
    int PostId, int Id, string Name, string Email, string Body);

Учитывая, что вы создали IHttpClientBuilder (httpClientBuilder) и теперь понимаете реализацию ExampleClient и соответствующую Comment модель, рассмотрим следующий пример:

httpClientBuilder.AddStandardResilienceHandler();

В приведенный выше код добавляется стандартный обработчик HttpClientустойчивости. Как и большинство API-интерфейсов устойчивости, существуют перегрузки, позволяющие настраивать параметры по умолчанию и применять стратегии устойчивости.

Стандартные обработчики устойчивости по умолчанию

Конфигурация по умолчанию объединяет пять стратегий устойчивости в следующем порядке (от самого внешнего к самому внутреннему):

Порядок Стратегия Description Defaults
1 Ограничение скорости Конвейер ограничения скорости ограничивает максимальное количество одновременных запросов, отправляемых в зависимость. Очередь: 0
Разрешать: 1_000
2 Общее время ожидания Общий конвейер времени ожидания запроса применяет общее время ожидания к выполнению, гарантируя, что запрос, включая попытки повторных попыток, не превышает настроенное ограничение. Общее время ожидания: 30s
3 Повторить попытку Конвейер повторных попыток повторяет запрос в случае замедления зависимости или возвращает временную ошибку. Максимальное число повторных попыток: 3
Backoff: Exponential
Используйте jitter: true
Задержка:2s
4 Средство разбиения цепи Средство разбиения цепи блокирует выполнение, если обнаружено слишком много прямых сбоев или времени ожидания. Соотношение сбоев: 10 %
Минимальная пропускная способность: 100
Длительность выборки: 30s
Длительность перерыва: 5s
5 Время ожидания попытки Конвейер времени ожидания попытки ограничивает длительность каждой попытки запроса и вызывает, если он превышен. Время ожидания попытки: 10s

Повторные попытки и разбиения цепи

Стратегии повторных попыток и останова цепи обрабатывают набор определенных кодов состояния HTTP и исключений. Рассмотрим следующие коды состояния HTTP:

  • HTTP 500 и более поздних версий (ошибки сервера)
  • HTTP 408 (время ожидания запроса)
  • HTTP 429 (слишком много запросов)

Кроме того, эти стратегии обрабатывают следующие исключения:

  • HttpRequestException
  • TimeoutRejectedException

Добавление стандартного обработчика hedging

Стандартный обработчик хэджинга упаковывает выполнение запроса со стандартным механизмом хеджирования. Хэджирование повторных попыток выполняет медленные запросы параллельно.

Чтобы использовать стандартный обработчик хэджинга, вызовите AddStandardHedgingHandler метод расширения. В следующем примере настраивается ExampleClient использование стандартного обработчика hedging.

httpClientBuilder.AddStandardHedgingHandler();

В предыдущий код добавляется стандартный обработчик HttpClientшестнадцатеричной обработки.

Стандартные обработчики хэджинга по умолчанию

Стандартное хеджирование использует пул разбиений цепи, чтобы гарантировать, что неработоспособные конечные точки не хеджируются. По умолчанию выбор из пула основан на url-адресе (схема + узел и порт).

Совет

Рекомендуется настроить способ выбора стратегий путем вызова StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority или StandardHedgingHandlerBuilderExtensions.SelectPipelineBy для более сложных сценариев.

В предыдущий код добавляется стандартный обработчик IHttpClientBuilderшестнадцатеричной обработки. Конфигурация по умолчанию объединяет пять стратегий устойчивости в следующем порядке (от самого внешнего к самому внутреннему):

Порядок Стратегия Description Defaults
1 Общее время ожидания запроса Общий конвейер времени ожидания запроса применяет общее время ожидания к выполнению, гарантируя, что запрос, включая попытки хеджирования, не превышает настроенное ограничение. Общее время ожидания: 30s
2 Хеджирование Стратегия хеджирования выполняет запросы к нескольким конечным точкам, если зависимость медленно или возвращает временную ошибку. Маршрутизация — это параметры, по умолчанию он просто хеджирует URL-адрес, предоставленный исходным HttpRequestMessage. Минимальные попытки: 1
Максимальное количество попыток: 10
Задержка: 2
3 Ограничение скорости (на конечную точку) Конвейер ограничения скорости ограничивает максимальное количество одновременных запросов, отправляемых в зависимость. Очередь: 0
Разрешать: 1_000
4 Разбиитель цепи (на конечную точку) Средство разбиения цепи блокирует выполнение, если обнаружено слишком много прямых сбоев или времени ожидания. Соотношение сбоев: 10 %
Минимальная пропускная способность: 100
Длительность выборки: 30s
Длительность перерыва: 5s
5 Время ожидания попытки (на конечную точку) Конвейер времени ожидания попытки ограничивает длительность каждой попытки запроса и вызывает, если он превышен. Время ожидания: 10s

Настройка выбора маршрута обработчика хэджинга

При использовании стандартного обработчика хеджирования можно настроить способ выбора конечных точек запроса путем вызова различных расширений в типе IRoutingStrategyBuilder . Это может быть полезно для таких сценариев, как тестирование A/B, где требуется направлять процент запросов в другую конечную точку:

httpClientBuilder.AddStandardHedgingHandler(static (IRoutingStrategyBuilder builder) =>
{
    // Hedging allows sending multiple concurrent requests
    builder.ConfigureOrderedGroups(static options =>
    {
        options.Groups.Add(new UriEndpointGroup()
        {
            Endpoints =
            {
                // Imagine a scenario where 3% of the requests are 
                // sent to the experimental endpoint.
                new() { Uri = new("https://example.net/api/experimental"), Weight = 3 },
                new() { Uri = new("https://example.net/api/stable"), Weight = 97 }
            }
        });
    });
});

Предыдущий код:

  • Добавляет обработчик шестнадцатеричного подключения в .IHttpClientBuilder
  • Настраивает IRoutingStrategyBuilder метод для ConfigureOrderedGroups настройки упорядоченных групп.
  • Добавляет в EndpointGroup orderedGroup конечную точку 3% запросов https://example.net/api/experimental и 97 % запросов к конечной точке https://example.net/api/stable .
  • Настраивает IRoutingStrategyBuilder ConfigureWeightedGroups метод для настройки

Чтобы настроить весовую группу, вызовите ConfigureWeightedGroups метод для IRoutingStrategyBuilder типа. Следующий пример настраивает IRoutingStrategyBuilder метод для ConfigureWeightedGroups настройки взвешированных групп.

httpClientBuilder.AddStandardHedgingHandler(static (IRoutingStrategyBuilder builder) =>
{
    // Hedging allows sending multiple concurrent requests
    builder.ConfigureWeightedGroups(static options =>
    {
        options.SelectionMode = WeightedGroupSelectionMode.EveryAttempt;

        options.Groups.Add(new WeightedUriEndpointGroup()
        {
            Endpoints =
            {
                // Imagine A/B testing
                new() { Uri = new("https://example.net/api/a"), Weight = 33 },
                new() { Uri = new("https://example.net/api/b"), Weight = 33 },
                new() { Uri = new("https://example.net/api/c"), Weight = 33 }
            }
        });
    });
});

Предыдущий код:

  • Добавляет обработчик шестнадцатеричного подключения в .IHttpClientBuilder
  • Настраивает IRoutingStrategyBuilder ConfigureWeightedGroups метод для настройки взвешированных групп.
  • Задает значение SelectionMode WeightedGroupSelectionMode.EveryAttempt.
  • Добавляет в WeightedEndpointGroup weightedGroup конечную точку 33% запросов https://example.net/api/a , 33% запросов к https://example.net/api/b конечной точке и 33% запросов к конечной точке https://example.net/api/c .

Совет

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

Дополнительные сведения см . в документации Полли: стратегия обеспечения устойчивости.

Обычно можно настроить упорядоченную группу или взвешнную группу, но это допустимо для настройки обоих. Использование упорядоченных и взвешированных групп полезно в сценариях, когда вы хотите отправить процент запросов в другую конечную точку, например, при тестировании A/B.

Добавление пользовательских обработчиков устойчивости

Чтобы получить дополнительные возможности управления, можно настроить обработчики устойчивости с помощью AddResilienceHandler API. Этот метод принимает делегат, который настраивает ResiliencePipelineBuilder<HttpResponseMessage> экземпляр, используемый для создания стратегий устойчивости.

Чтобы настроить именованный обработчик устойчивости, вызовите AddResilienceHandler метод расширения с именем обработчика. В следующем примере настраивается именованный "CustomPipeline"обработчик устойчивости.

httpClientBuilder.AddResilienceHandler(
    "CustomPipeline",
    static builder =>
{
    // See: https://www.pollydocs.org/strategies/retry.html
    builder.AddRetry(new HttpRetryStrategyOptions
    {
        // Customize and configure the retry logic.
        BackoffType = DelayBackoffType.Exponential,
        MaxRetryAttempts = 5,
        UseJitter = true
    });

    // See: https://www.pollydocs.org/strategies/circuit-breaker.html
    builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
    {
        // Customize and configure the circuit breaker logic.
        SamplingDuration = TimeSpan.FromSeconds(10),
        FailureRatio = 0.2,
        MinimumThroughput = 3,
        ShouldHandle = static args =>
        {
            return ValueTask.FromResult(args is
            {
                Outcome.Result.StatusCode:
                    HttpStatusCode.RequestTimeout or
                        HttpStatusCode.TooManyRequests
            });
        }
    });

    // See: https://www.pollydocs.org/strategies/timeout.html
    builder.AddTimeout(TimeSpan.FromSeconds(5));
});

Предыдущий код:

  • Добавляет обработчик устойчивости с именем "CustomPipeline" в качестве pipelineName контейнера службы.
  • Добавляет стратегию повторных попыток с экспоненциальным обратным выходом, пятью повторными попытками и предпочтением jitter в построителе устойчивости.
  • Добавляет стратегию разбиения цепи с длительностью выборки в 10 секунд, соотношение сбоев от 0,2 (20%), минимальная пропускная способность 3, а также предикат, обрабатывающий RequestTimeout и TooManyRequests коды состояния HTTP, в построителе устойчивости.
  • Добавляет стратегию тайм-аута с истечением 5 секунд в построителе устойчивости.

Существует множество вариантов для каждой стратегии устойчивости. Дополнительные сведения см. в документации Полли : Стратегии. Дополнительные сведения о настройке ShouldHandle делегатов см . в документах Polly: обработка ошибок в реактивных стратегиях.

Динамическая перезагрузка

Polly поддерживает динамическую перезагрузку настроенных стратегий устойчивости. Это означает, что можно изменить конфигурацию стратегий устойчивости во время выполнения. Чтобы включить динамическую перезагрузку, используйте соответствующую AddResilienceHandler перегрузку, которая предоставляет ResilienceHandlerContext. Учитывая контекст, вызов EnableReloads соответствующих параметров стратегии устойчивости:

httpClientBuilder.AddResilienceHandler(
    "AdvancedPipeline",
    static (ResiliencePipelineBuilder<HttpResponseMessage> builder,
        ResilienceHandlerContext context) =>
    {
        // Enable reloads whenever the named options change
        context.EnableReloads<HttpRetryStrategyOptions>("RetryOptions");

        // Retrieve the named options
        var retryOptions =
            context.GetOptions<HttpRetryStrategyOptions>("RetryOptions");

        // Add retries using the resolved options
        builder.AddRetry(retryOptions);
    });

Предыдущий код:

  • Добавляет обработчик устойчивости с именем "AdvancedPipeline" в качестве pipelineName контейнера службы.
  • Включает перезагрузку конвейера "AdvancedPipeline" при изменении именованных RetryStrategyOptions параметров.
  • Извлекает именованные параметры из IOptionsMonitor<TOptions> службы.
  • Добавляет стратегию повторных попыток с извлеченными параметрами в построитель устойчивости.

Дополнительные сведения см . в документации Полли: расширенная внедрение зависимостей.

В этом примере используется раздел параметров, который может изменяться, например файл appsettings.json . Рассмотрите следующий файл appsettings.json:

{
    "RetryOptions": {
        "Retry": {
            "BackoffType": "Linear",
            "UseJitter": false,
            "MaxRetryAttempts": 7
        }
    }
}

Теперь представьте, что эти параметры привязаны к конфигурации приложения, привязав его HttpRetryStrategyOptions к разделу "RetryOptions" :

var section = builder.Configuration.GetSection("RetryOptions");

builder.Services.Configure<HttpStandardResilienceOptions>(section);

Дополнительные сведения см . в разделе "Параметры" в .NET.

Пример использования

Ваше приложение использует внедрение зависимостей для разрешения ExampleClient и соответствующего HttpClientприложения. Код создает IServiceProvider и разрешает его ExampleClient .

IHost host = builder.Build();

ExampleClient client = host.Services.GetRequiredService<ExampleClient>();

await foreach (Comment? comment in client.GetCommentsAsync())
{
    Console.WriteLine(comment);
}

Предыдущий код:

  • Создает сборку IServiceProvider ServiceCollectionиз .
  • Разрешает из ExampleClient .a0 IServiceProvider>.
  • Вызывает метод для GetCommentsAsync ExampleClient получения комментариев.
  • Записывает каждый комментарий в консоль.

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

Пример рабочего потока HTTP GET с конвейером устойчивости.

На приведенной выше схеме показано следующее:

  • Запрос ExampleClient HTTP GET отправляется в конечную точку /comments .
  • Вычисляется HttpResponseMessage :
    • Если ответ выполнен успешно (HTTP 200), возвращается ответ.
    • Если ответ не удается (HTTP не 200), конвейер устойчивости использует настроенные стратегии устойчивости.

Хотя это простой пример, он демонстрирует, как стратегии устойчивости можно использовать для обработки временных ошибок. Дополнительные сведения см . в документации Полли: Стратегии.

Известные проблемы

В следующих разделах подробно описаны различные известные проблемы.

Совместимость с пакетом Grpc.Net.ClientFactory

Если вы используете Grpc.Net.ClientFactory версию или более раннюю версию 2.63.0 , включите стандартные обработчики устойчивости или хэджинга для клиента gRPC может вызвать исключение среды выполнения. В частности, рассмотрим следующий пример кода:

services
    .AddGrpcClient<Greeter.GreeterClient>()
    .AddStandardResilienceHandler();

Предыдущий код приводит к следующему исключению:

System.InvalidOperationException: The ConfigureHttpClient method is not supported when creating gRPC clients. Unable to create client with name 'GreeterClient'.

Чтобы устранить эту проблему, рекомендуется обновить до Grpc.Net.ClientFactory версии 2.64.0 или более поздней.

Существует проверка времени сборки, которая проверяет, используете Grpc.Net.ClientFactory ли вы версию или более раннюю версию 2.63.0 , и если вы создаете предупреждение о компиляции. Предупреждение можно отключить, задав в файле проекта следующее свойство:

<PropertyGroup>
  <SuppressCheckGrpcNetClientFactoryVersion>true</SuppressCheckGrpcNetClientFactoryVersion>
</PropertyGroup>

Совместимость с .NET Application Insights

Если вы используете .NET Application Insights, то включение функций устойчивости в приложении может привести к тому, что все данные телеметрии Application Insights отсутствуют. Проблема возникает при регистрации функций устойчивости перед службами Application Insights. Рассмотрим следующий пример, вызывающий проблему:

// At first, we register resilience functionality.
services.AddHttpClient().AddStandardResilienceHandler();

// And then we register Application Insights. As a result, Application Insights doesn't work.
services.AddApplicationInsightsTelemetry();

Эта проблема вызвана следующей ошибкой в Application Insights и может быть исправлена путем регистрации служб Application Insights перед функциональными возможностями устойчивости, как показано ниже:

// We register Application Insights first, and now it will be working correctly.
services.AddApplicationInsightsTelemetry();
services.AddHttpClient().AddStandardResilienceHandler();