복원력 있는 HTTP 앱 빌드: 주요 개발 패턴
일시적인 오류를 복구할 수 있는 강력한 HTTP 앱을 빌드하는 것은 일반적인 요구 사항입니다. 이 문서에서는 전달된 핵심 개념을 확장하므로 복원력 있는 앱 개발 소개를 이미 읽었다고 가정합니다. 복원력 있는 HTTP 앱을 빌드하는 데 도움이 되도록 Microsoft.Extensions.Http.Resilience NuGet 패키지는 특히 HttpClient에 대한 복원력 메커니즘을 제공합니다. 이 NuGet 패키지는 자주 사용되는 오픈 소스 프로젝트인 Microsoft.Extensions.Resilience
라이브러리와 Polly를 사용합니다. 자세한 내용은 Polly를 참조하세요.
시작하기
HTTP 앱에서 복원력 패턴을 사용하려면 Microsoft.Extensions.Http.Resilience NuGet 패키지를 설치합니다.
dotnet add package Microsoft.Extensions.Http.Resilience --version 8.0.0
자세한 내용은 dotnet add package 또는 .NET 애플리케이션에서 패키지 종속성 관리를 참조하세요.
HTTP 클라이언트에 복원력 추가
HttpClient에 복원력을 추가하려면 사용 가능한 AddHttpClient 메서드 호출에서 반환된 IHttpClientBuilder 형식에 대한 호출을 연결합니다. 자세한 내용은 .NET을 사용한 IHttpClientFactory를 참조하세요.
여러 가지 복원력 중심 확장을 사용할 수 있습니다. 일부는 표준이므로 다양한 업계 모범 사례를 채택하고 다른 일부는 사용자 지정이 가능합니다. 복원력을 추가할 때 복원력 처리기를 하나만 추가하고 처리기를 겹쳐서는 안 됩니다. 여러 복원력 처리기를 추가해야 하는 경우 복원력 전략을 사용자 지정할 수 있는 AddResilienceHandler
확장 메서드 사용을 고려해야 합니다.
Important
이 문서에 포함된 모든 예는 IHttpClientBuilder 인스턴스를 반환하는 Microsoft.Extensions.Http 라이브러리의 AddHttpClient API를 사용합니다. IHttpClientBuilder 인스턴스는 HttpClient를 구성하고 복원력 처리기를 추가하는 데 사용됩니다.
표준 복원력 처리기 추가
표준 복원력 처리기는 요청을 보내고 일시적인 오류를 처리하는 기본 옵션과 함께 서로 겹쳐진 여러 복원력 전략을 사용합니다. 표준 복원력 처리기는 IHttpClientBuilder 인스턴스에서 AddStandardResilienceHandler
확장 메서드를 호출하여 추가됩니다.
var services = new ServiceCollection();
var httpClientBuilder = services.AddHttpClient<ExampleClient>(
configureClient: static client =>
{
client.BaseAddress = new("https://jsonplaceholder.typicode.com");
});
앞의 코드가 하는 역할은 다음과 같습니다.
- ServiceCollection 인스턴스를 만듭니다.
- 서비스 컨테이너에
ExampleClient
형식에 대한 HttpClient를 추가합니다. "https://jsonplaceholder.typicode.com"
을 기준 주소로 사용하도록 HttpClient를 구성합니다.- 이 문서의 다른 예 전반에 걸쳐 사용되는
httpClientBuilder
를 만듭니다.
좀 더 실제적인 예는 .NET 일반 호스트 문서에 설명된 것과 같은 호스팅에 의존하는 것입니다. Microsoft.Extensions.Hosting NuGet 패키지를 사용하여 다음 업데이트된 예를 고려합니다.
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");
}
}
앞의 코드가 하는 역할은 다음과 같습니다.
- HttpClient를 허용하는 생성자가 있는
ExampleClient
형식을 정의합니다. - GET 요청을
/comments
엔드포인트로 보내고 응답을 반환하는GetCommentsAsync
메서드를 노출합니다.
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와 마찬가지로 기본 옵션과 적용된 복원력 전략을 사용자 지정할 수 있는 오버로드가 있습니다.
표준 복원력 처리기 기본값
기본 구성은 다음 순서(가장 바깥쪽부터 가장 안쪽까지)로 5개의 복원력 전략을 연결합니다.
순서 | 전략 | 설명 | Defaults |
---|---|---|---|
1 | 속도 제한기 | 속도 제한기 파이프라인은 종속성으로 전송되는 최대 동시 요청 수를 제한합니다. | 큐: 0 허용: 1_000 |
2 | 총 시간 제한 | 총 요청 제한 시간 파이프라인은 실행에 전체 제한 시간을 적용하여 다시 시도를 포함한 요청이 구성된 제한을 초과하지 않도록 합니다. | 총 시간 제한: 30초 |
3 | 재시도 | 다시 시도 파이프라인은 종속성이 느리거나 일시적인 오류를 반환하는 경우 요청을 다시 시도합니다. | 최대 시도: 3 백오프: Exponential 지터 사용: true 지연: 2초 |
4 | 회로 차단기 | 너무 많은 직접 실패나 시간 제한이 검색되면 회로 차단기가 실행을 차단합니다. | 실패율: 10% 최소 처리량: 100 샘플링 기간: 30초 휴식 시간: 5초 |
5 | 시도 시간 제한 | 시도 제한 시간 파이프라인은 각 요청 시도 기간을 제한하고 초과되면 오류를 발생시킵니다. | 시도 시간 제한: 10초 |
다시 시도 및 회로 차단기
다시 시도 및 회로 차단기 전략은 모두 특정 HTTP 상태 코드 및 예외 집합을 처리합니다. 다음 HTTP 상태 코드를 고려합니다.
- HTTP 500 이상(서버 오류)
- HTTP 408(요청 시간 초과)
- HTTP 429(요청이 너무 많음)
또한 이러한 전략은 다음 예외를 처리합니다.
HttpRequestException
TimeoutRejectedException
표준 Hedging 처리기 추가
표준 Hedging 처리기는 표준 Hedging 메커니즘을 사용하여 요청 실행을 래핑합니다. Hedging은 느린 요청을 병렬로 다시 시도합니다.
표준 Hedging 처리기를 사용하려면 AddStandardHedgingHandler
확장 메서드를 호출합니다. 다음 예에서는 표준 Hedging 처리기를 사용하도록 ExampleClient
를 구성합니다.
httpClientBuilder.AddStandardHedgingHandler();
앞의 코드는 표준 Hedging 처리기를 HttpClient에 추가합니다.
표준 Hedging 처리기 기본값
표준 Hedging에서는 회로 차단기 풀을 사용하여 비정상 엔드포인트가 Hedging되지 않도록 보장합니다. 기본적으로 풀의 선택은 URL 권한(구성표 + 호스트 + 포트)을 기반으로 합니다.
팁
고급 시나리오의 경우 StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority
또는 StandardHedgingHandlerBuilderExtensions.SelectPipelineBy
를 호출하여 전략이 선택되는 방식을 구성하는 것이 좋습니다.
앞의 코드는 표준 Hedging 처리기를 IHttpClientBuilder에 추가합니다. 기본 구성은 다음 순서(가장 바깥쪽부터 가장 안쪽까지)로 5개의 복원력 전략을 연결합니다.
순서 | 전략 | 설명 | Defaults |
---|---|---|---|
1 | 총 요청 시간 초과 | 총 요청 제한 시간 파이프라인은 실행에 전체 제한 시간을 적용하여 Hedging 시도를 포함한 요청이 구성된 제한을 초과하지 않도록 합니다. | 총 시간 제한: 30초 |
2 | Hedging | Hedging 전략은 종속성이 느리거나 일시적인 오류를 반환하는 경우 여러 엔드포인트에 대해 요청을 실행합니다. 라우팅은 옵션이며 기본적으로 원래 HttpRequestMessage에서 제공한 URL을 헤지합니다. | 최소 시도 횟수: 1 최대 시도 횟수: 10 지연: 2초 |
3 | 속도 제한기(엔드포인트당) | 속도 제한기 파이프라인은 종속성으로 전송되는 최대 동시 요청 수를 제한합니다. | 큐: 0 허용: 1_000 |
4 | 회로 차단기(엔드포인트당) | 너무 많은 직접 실패나 시간 제한이 검색되면 회로 차단기가 실행을 차단합니다. | 실패율: 10% 최소 처리량: 100 샘플링 기간: 30초 휴식 시간: 5초 |
5 | 시도 시간 제한(엔드포인트당) | 시도 제한 시간 파이프라인은 각 요청 시도 기간을 제한하고 초과되면 오류를 발생시킵니다. | 시간 제한: 10초 |
Hedging 처리기 경로 선택 사용자 지정
표준 Hedging 처리기를 사용할 때 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에 Hedging 처리기를 추가합니다.
ConfigureOrderedGroups
메서드를 사용하여 순서가 지정된 그룹을 구성하도록IRoutingStrategyBuilder
를 구성합니다.- 요청의 3%를
https://example.net/api/experimental
엔드포인트로 라우팅하고 요청의 97%를https://example.net/api/stable
엔드포인트로 라우팅하는orderedGroup
에EndpointGroup
을 추가합니다. ConfigureWeightedGroups
메서드를 사용하여 구성하도록IRoutingStrategyBuilder
를 구성합니다.
가중치 적용 그룹을 구성하려면 IRoutingStrategyBuilder
형식에서 ConfigureWeightedGroups
메서드를 호출합니다. 다음 예에서는 ConfigureWeightedGroups
메서드를 사용하여 가중치 적용 그룹을 구성하도록 IRoutingStrategyBuilder
를 구성합니다.
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에 Hedging 처리기를 추가합니다.
- 가중치 적용 그룹을 구성하기 위해
ConfigureWeightedGroups
메서드를 사용하도록IRoutingStrategyBuilder
를 구성합니다. SelectionMode
를WeightedGroupSelectionMode.EveryAttempt
으로 설정합니다.- 요청의 33%를
https://example.net/api/a
엔드포인트로, 요청의 33%를https://example.net/api/b
엔드포인트로, 요청의 33%를https://example.net/api/c
엔드포인트로 라우팅하는weightedGroup
에WeightedEndpointGroup
을 추가합니다.
팁
최대 Hedging 시도 횟수는 구성된 그룹 수와 직접적으로 연관됩니다. 예를 들어, 그룹이 2개 있는 경우 최대 시도 횟수는 2입니다.
자세한 내용은 Polly 문서: Hedging 복원력 전략을 참조하세요.
순서가 지정된 그룹이나 가중치가 부여된 그룹을 구성하는 것이 일반적이지만 둘 다 구성하는 것도 유효합니다. 순서가 지정된 가중치 그룹을 사용하는 것은 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
으로 추가합니다. - 복원력 작성기에 지수 백오프, 5회 다시 시도 및 지터 기본 설정이 포함된 다시 시도 전략을 추가합니다.
- 샘플링 기간 10초, 실패율 0.2(20%), 최소 처리량 3,
RequestTimeout
및TooManyRequests
HTTP 상태 코드를 처리하는 조건자를 포함하는 회로 차단기 전략을 복원력 작성기에 추가합니다. - 복원력 작성기에 5초의 시간 제한이 있는 시간 제한 전략을 추가합니다.
각 복원력 전략에 사용할 수 있는 다양한 옵션이 있습니다. 자세한 내용은 Polly 문서: 전략을 참조하세요. ShouldHandle
대리자 구성에 대한 자세한 내용은 Polly 문서: 대응 전략의 오류 처리를 참조하세요.
동적 새로 고침
Polly는 구성된 복원력 전략의 동적 다시 로드를 지원합니다. 이는 런타임에 복원력 전략의 구성을 변경할 수 있음을 의미합니다. 동적 다시 로드를 사용하도록 설정하려면 ResilienceHandlerContext
를 노출하는 적절한 AddResilienceHandler
오버로드를 사용합니다. 컨텍스트가 주어지면 해당 복원력 전략 옵션의 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
으로 추가합니다. - 명명된
RetryStrategyOptions
옵션이 변경될 때마다"AdvancedPipeline"
파이프라인을 다시 로드할 수 있습니다. - IOptionsMonitor<TOptions> 서비스에서 명명된 옵션을 검색합니다.
- 복원력 작성기에 검색된 옵션이 포함된 다시 시도 전략을 추가합니다.
자세한 내용은 Polly 문서: 고급 종속성 주입을 참조하세요.
이 예는 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);
}
앞의 코드가 하는 역할은 다음과 같습니다.
- ServiceCollection에서 IServiceProvider를 빌드합니다.
- IServiceProvider에서
ExampleClient
를 확인합니다. - 주석을 가져오기 위해
ExampleClient
에서GetCommentsAsync
메서드를 호출합니다. - 각 주석을 콘솔에 씁니다.
네트워크가 다운되거나 서버가 응답하지 않는 상황을 상상해 보세요. 다음 다이어그램은 ExampleClient
및 GetCommentsAsync
메서드를 고려하여 복원력 전략이 상황을 처리하는 방법을 보여 줍니다.
앞의 다이어그램은 다음을 보여 줍니다.
ExampleClient
는 HTTP GET 요청을/comments
엔드포인트로 보냅니다.- HttpResponseMessage가 평가됩니다.
- 응답이 성공하면(HTTP 200) 응답이 반환됩니다.
- 응답이 실패하면(HTTP 200이 아님) 복원력 파이프라인은 구성된 복원력 전략을 사용합니다.
이는 간단한 예이지만 복원력 전략을 사용하여 일시적인 오류를 처리하는 방법을 보여 줍니다. 자세한 내용은 Polly 문서: 전략을 참조하세요.
알려진 문제
다음 섹션에서는 알려진 다양한 문제에 대해 자세히 알아봅니다.
Grpc.Net.ClientFactory
패키지와의 호환성
Grpc.Net.ClientFactory
버전 2.63.0
이하를 사용하는 경우, gRPC 클라이언트에 대해 표준 복원력 또는 hedging 처리기를 사용하도록 설정하면 런타임 예외가 발생할 수 있습니다. 특히 다음 코드 샘플을 고려합니다.
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();
.NET