Создание устойчивых 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
метод:
На приведенной выше схеме показано следующее:
- Запрос
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();