WebAPI 클라이언트에서 호출 당 새로운 HttpClient를 작성하는 오버 헤드는 무엇입니까?


162

HttpClientWebAPI 클라이언트 의 수명은 어떻게됩니까? 여러 통화에 대해
하나의 인스턴스를 갖는 것이 더 낫 HttpClient습니까?

HttpClient아래 예와 같이 요청 당 생성 및 처리의 오버 헤드는 무엇입니까 ( http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from- 에서 가져옴) a-net-client ) :

using (var client = new HttpClient())
{
    client.BaseAddress = new Uri("http://localhost:9000/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    // New code:
    HttpResponseMessage response = await client.GetAsync("api/products/1");
    if (response.IsSuccessStatusCode)
    {
        Product product = await response.Content.ReadAsAsync<Product>();
        Console.WriteLine("{0}\t${1}\t{2}", product.Name, product.Price, product.Category);
    }
}

Stopwatch그러나 클래스를 사용하여 벤치마킹 할 수 있는지 확실하지 않습니다 . 내 예상은 HttpClient모든 인스턴스가 동일한 컨텍스트에서 사용된다고 가정하면 단일을 갖는 것이 더 합리적 이라고 생각합니다.
Matthew

답변:


215

HttpClient여러 통화에 대해 다시 사용할 수 있도록 설계되었습니다 . 여러 스레드에서도 가능합니다. 는 HttpClientHandler다시 사용 통화를 통해 의도하는 자격 증명과 쿠키가 있습니다. 새로운 HttpClient인스턴스를 가지려면 모든 것을 재설정해야합니다. 또한이 DefaultRequestHeaders속성에는 여러 호출을위한 속성이 포함되어 있습니다. 각 요청에서 해당 값을 재설정하면 요점을 잃습니다.

또 다른 주요 이점은 요청 / 응답 파이프 라인에 HttpClient추가 HttpMessageHandlers하여 교차 절단 문제를 적용 할 수 있다는 것입니다. 로깅, 감사, 제한, 리디렉션 처리, 오프라인 처리, 메트릭 캡처에 사용될 수 있습니다. 모든 종류의 다른 것들. 각 요청에 대해 새로운 HttpClient가 작성되는 경우, 각 메시지 핸들러는 각 요청마다 설정되어야하며 이러한 핸들러에 대한 요청간에 공유되는 애플리케이션 레벨 상태도 제공되어야합니다.

의 기능을 많이 사용 HttpClient할수록 기존 인스턴스를 재사용하는 것이 더 합리적임을 알 수 있습니다.

그러나 가장 큰 문제는 HttpClient클래스가 폐기되면을 삭제 HttpClientHandler하고 TCP/IP에서 관리하는 연결 풀에서 연결 을 강제로 닫는다는 것입니다 ServicePointManager. 이는 새로운 요청이있을 때마다 HttpClient새로운 TCP/IP연결을 다시 설정해야 함을 의미 합니다.

내 테스트에서 LAN에서 일반 HTTP를 사용하면 성능 저하가 거의 무시할 수 있습니다. 연결 HttpClientHandler을 닫으려고 할 때도 연결을 열어두고있는 기본 TCP keepalive가 있기 때문에 이것이 의심 됩니다.

인터넷을 통한 요청에서 나는 다른 이야기를 보았습니다. 매번 요청을 다시 열어야하기 때문에 40 %의 성능 저하를 경험했습니다.

HTTPS연결 에 대한 적중 이 더 나빠질 것으로 생각 됩니다.

내 조언은 연결하는 각 개별 API에 대해 애플리케이션 수명 동안 HttpClient 인스턴스유지하는 것입니다.


5
which then forcibly closes the TCP/IP connection in the pool of connections that is managed by ServicePointManager이 진술에 대해 얼마나 확신하십니까? 믿기 ​​어렵습니다. HttpClient자주 인스턴스화해야하는 작업 단위처럼 보입니다.
usr

2
@vkelman 그렇습니다. 새로운 HttpClientHandler로 HttpClient를 생성 한 경우에도 HttpClient 인스턴스를 재사용 할 수 있습니다. 또한 HttpClient에는 HttpClientHandler를 재사용하고 연결을 끊지 않고 HttpClient를 처리 할 수있는 특수 생성자가 있습니다.
Darrel Miller

2
@vkelman 나는 HttpClient를 유지하는 것을 선호하지만 HttpClientHandler를 유지하는 것을 선호한다면 두 번째 매개 변수가 false 일 때 연결을 유지합니다.
Darrel Miller

2
@DarrelMiller 따라서 연결이 HttpClientHandler에 바인딩 된 것처럼 들립니다. 나는 확장을 위해 연결을 파괴하고 싶지 않기 때문에 HttpClientHandler를 유지하고 그로부터 모든 HttpClient 인스턴스를 만들거나 정적 HttpClient 인스턴스를 만들어야합니다. 그러나 CookieContainer가 HttpClientHandler에 바인딩되어 있고 요청에 따라 쿠키가 달라야하는 경우 어떻게 권장합니까? 각 요청에 대해 CookieContainer를 수정하여 정적 HttpClientHandler에서 스레드 동기화를 피하고 싶습니다.
Dave Black

2
@ Sana.91 할 수 있습니다. 서비스 콜렉션에서 싱글 톤으로 등록하고 그런 식으로 액세스하는 것이 좋습니다.
Darrel Miller

69

응용 프로그램을 확장하려면 차이가 큽니다! 부하에 따라 성능 수치가 매우 다릅니다. Darrel Miller가 언급했듯이 HttpClient는 여러 요청에서 재사용되도록 설계되었습니다. 이것은 BCL 팀의 직원들에 의해 확인되었습니다.

내가 최근에 수행 한 프로젝트는 매우 유명한 대형 온라인 컴퓨터 소매 업체가 일부 새로운 시스템의 Black Friday / holiday 트래픽으로 확장 할 수 있도록 돕는 것이 었습니다. HttpClient 사용과 관련된 몇 가지 성능 문제가 발생했습니다. 그것은 구현하기 때문에 개발자 IDisposable는 인스턴스를 생성하고 using()명령문 안에 배치하여 일반적으로하는 일을했습니다 . 우리가로드 테스트를 시작하면 앱이 서버를 무릎 꿇게했습니다. 예, 서버는 앱만이 아닙니다. 그 이유는 모든 HttpClient 인스턴스가 서버에서 포트를 열기 때문입니다. GC의 결정적이지 않은 마무리와 여러 OSI 계층에 걸쳐있는 컴퓨터 리소스를 사용하고 있기 때문에 네트워크 포트를 닫는 데 시간이 오래 걸릴 수 있습니다. 실제로 Windows OS 자체포트를 닫는 데 최대 20 초가 소요될 수 있습니다 (Microsoft 당). 우리는 닫힐 수있는 것보다 더 빨리 포트를 열었습니다. 서버 포트 소진으로 인해 CPU가 100 %로 떨어졌습니다. 내 문제는 HttpClient를 정적 인스턴스로 변경하여 문제를 해결하는 것이 었습니다. 예, 일회용 자원이지만 성능의 차이로 인해 오버 헤드가 훨씬 더 큽니다. 앱의 작동 방식을 확인하기 위해로드 테스트를 수행하는 것이 좋습니다.

https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client 에서 설명서 및 예제에 대한 WebAPI 지침 페이지를 확인할 수도 있습니다.

이 문구에 특히주의하십시오 :

HttpClient는 한 번 인스턴스화되어 응용 프로그램 수명 동안 재사용됩니다. 특히 서버 애플리케이션에서 모든 요청에 ​​대해 새 HttpClient 인스턴스를 작성하면로드가 많은 경우 사용 가능한 소켓 수가 소모됩니다. SocketException 오류가 발생합니다.

HttpClient다른 헤더, 기본 주소 등 을 사용하여 정적을 사용해야하는 경우 에는HttpRequestMessage 수동으로 해당 값을 설정해야합니다 HttpRequestMessage. 그런 다음HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)

.NET Core 용 업데이트 : IHttpClientFactoryvia Dependency Injection을 사용하여 HttpClient인스턴스 를 만들어야 합니다. 수명을 관리하므로 명시 적으로 폐기 할 필요가 없습니다. ASP.NET Core에서 IHttpClientFactory를 사용하여 HTTP 요청 만들기를 참조하십시오.


1
이 포스트는 스트레스 테스트를 할 사람들에게 유용한 통찰력을 담고 있습니다 ..!
Sana.91

9

다른 답변 상태 HttpClient는 재사용을 의미합니다. 그러나, 하나의 재사용 HttpClient다중 스레드 응용 프로그램의 수단을 통해 인스턴스를 당신이 좋아, 그 상태 속성 값을 변경할 수 없습니다 BaseAddressDefaultRequestHeaders (따라서 응용 프로그램에서 일정한 경우에만 사용할 수 있음).

이 제한을 주변에 얻기를위한 한 가지 방법은 포장입니다 HttpClient모든 복제 클래스와 HttpClient당신이 필요로하는 방법을 ( GetAsync, PostAsync싱글로 등) 위임을 HttpClient. 그러나 그것은 매우 지루합니다 ( 확장 메소드 도 래핑해야합니다 ). 다행히도 새로운 인스턴스를 계속 생성 하지만 기본을 재사용하는 또 다른 방법이 있습니다. 핸들러를 폐기하지 마십시오.HttpClientHttpClientHandler

HttpClientHandler _sharedHandler = new HttpClientHandler(); //never dispose this
HttpClient GetClient(string token)
{
    //client code can dispose these HttpClient instances
    return new HttpClient(_sharedHandler, disposeHandler: false)         
    {
       DefaultRequestHeaders = 
       {
            Authorization = new AuthenticationHeaderValue("Bearer", token) 
       } 
    };
}

2
더 좋은 방법은 하나의 HttpClient 인스턴스를 유지 한 다음 자체 로컬 HttpRequestMessage 인스턴스를 만든 다음 HttpClient에서 .SendAsync () 메서드를 사용하는 것입니다. 이 방법으로 여전히 스레드 안전합니다. 각 HttpRequestMessage에는 자체 인증 / URL 값이 있습니다.
Tim P.

@TimP. 왜 더 낫습니까? SendAsync훨씬 덜 편리 같은 전용 방법보다 PutAsync, PostAsJsonAsync
오핫 슈나이더

2
SendAsync를 사용하면 URL 및 헤더와 같은 기타 속성을 변경하고 스레드로부터 안전합니다.
Tim P.

2
예, 핸들러가 핵심입니다. 그것이 HttpClient 인스턴스간에 공유되는 한 괜찮습니다. 나는 당신의 이전 의견을 잘못 읽었습니다.
Dave Black

1
공유 처리기를 유지하는 경우 여전히 오래된 DNS 문제를 처리해야합니까?
shanti

5

대용량 웹 사이트와 관련이 있지만 HttpClient와 직접 관련이 없습니다. 모든 서비스에 아래 코드 스 니펫이 있습니다.

        // number of milliseconds after which an active System.Net.ServicePoint connection is closed.
        const int DefaultConnectionLeaseTimeout = 60000;

        ServicePoint sp =
                ServicePointManager.FindServicePoint(new Uri("http://<yourServiceUrlHere>"));
        sp.ConnectionLeaseTimeout = DefaultConnectionLeaseTimeout;

에서 ) https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Net.ServicePoint.ConnectionLeaseTimeout);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2; k (DevLang-csharp) & rd = true

"ServicePoint 개체의 활성 연결이 무기한으로 열려 있지 않도록이 속성을 사용할 수 있습니다.이 속성은로드 밸런싱 시나리오와 같이 연결을 주기적으로 삭제하고 재설정해야하는 시나리오를위한 것입니다.

기본적으로 요청에 대해 KeepAlive가 true 인 경우 MaxIdleTime 속성은 비활성으로 인해 ServicePoint 연결을 닫는 시간 제한을 설정합니다. ServicePoint에 활성 연결이있는 경우 MaxIdleTime은 영향을 미치지 않으며 연결은 무기한으로 열려 있습니다.

ConnectionLeaseTimeout 속성이 -1 이외의 값으로 설정되고 지정된 시간이 경과하면 요청에서 KeepAlive를 false로 설정하여 요청을 처리 한 후 활성 ServicePoint 연결이 닫힙니다. 이 값을 설정하면 ServicePoint 개체가 관리하는 모든 연결에 영향을줍니다. "

장애 조치하려는 CDN 또는 기타 엔드 포인트 뒤에 서비스가있는 경우이 설정은 발신자가 사용자를 새로운 목적지로 안내합니다. 이 예에서는 장애 조치 후 60 초 후에 모든 발신자가 새 끝점에 다시 연결해야합니다. 종속 서비스 (전화하는 서비스) 및 해당 엔드 포인트를 알아야합니다.


연결을 열고 닫음으로써 여전히 서버에 많은 부하를가합니다. 인스턴스 기반 HttpClients를 인스턴스 기반 HttpClientHandlers와 함께 사용하는 경우주의하지 않으면 여전히 포트 소진이 발생합니다.
Dave Black

동의하지 않습니다. 모든 것이 절충입니다. 우리에게 재정비 된 CDN 또는 DNS를 따르는 것은 은행의 돈과 수익 손실입니다.
환불 불가 환불 불가

1

Simon Timms의이 블로그 게시물을 참조 할 수도 있습니다. https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

그러나 HttpClient다릅니다. IDisposable인터페이스를 구현하지만 실제로는 공유 객체입니다. 즉, 덮개 아래에 재진입이 가능하며 나사산이 안전합니다. HttpClient각 실행마다 새 인스턴스를 생성하는 대신 HttpClient애플리케이션의 전체 수명 동안 단일 인스턴스를 공유해야 합니다. 왜 그런지 보자.


1

"사용하지 마십시오"블로그 참고 사항 중 어느 것도 고려할 필요가없는 BaseAddress 및 DefaultHeader가 아니라는 점을 지적해야합니다. HttpClient를 정적으로 만들면 요청을 통해 전달되는 내부 상태가 있습니다. 예 : HttpClient를 사용하여 FedAuth 토큰을 얻기 위해 HttpClient를 사용하여 타사에 인증하고 있는데 (OAuth / OWIN / etc를 사용하지 않는 이유는 무시) 응답 메시지에 FedAuth에 대한 Set-Cookie 헤더가 있으며 이는 HttpClient 상태에 추가됩니다. 각 요청에서 이러한 쿠키를 관리하지 않는 한 API에 로그인 한 다음 사용자는 마지막 사람의 FedAuth 쿠키를 보냅니다.


0

첫 번째 문제로,이 클래스는 일회용이지만 using명령문 과 함께 사용하는 것은 처분 할 때도 최선의 선택이 아닙니다HttpClient 객체 기본 소켓이 즉시 해제되지 않고 '소켓 고갈'이라는 심각한 문제가 발생할 수 있으므로 이 아닙니다.

그러나 HttpClient싱글 톤 또는 정적 객체로 사용할 때 가질 수 있는 두 번째 문제가 있습니다. 이 경우 싱글 톤 또는 정적 HttpClient은 존중하지 않습니다.DNS 변경 사항을 .

.net 코어 에서 HttpClientFactory로 다음과 같은 작업을 수행 할 수 있습니다 .

public interface IBuyService
{
    Task<Buy> GetBuyItems();
}
public class BuyService: IBuyService
{
    private readonly HttpClient _httpClient;

    public BuyService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<Buy> GetBuyItems()
    {
        var uri = "Uri";

        var responseString = await _httpClient.GetStringAsync(uri);

        var buy = JsonConvert.DeserializeObject<Buy>(responseString);
        return buy;
    }
}

서비스 구성

services.AddHttpClient<IBuyService, BuyService>(client =>
{
     client.BaseAddress = new Uri(Configuration["BaseUrl"]);
});

여기에 문서와 예제

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.