WebAPIクライアントのHttpClient
ライフタイムはどうなりますか?
複数の呼び出しに対してHttpClient
のインスタンスを1つ持つ方がよいでしょうか?
次の例のように、リクエストごとに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);
}
}
HttpClient
は複数の呼び出しで再利用されるように設計されていますです。複数のスレッド間でも。 HttpClientHandler
には、呼び出し間で再利用するための資格情報とCookieがあります。新しいHttpClient
インスタンスを作成するには、そのすべてを再設定する必要があります。また、DefaultRequestHeaders
プロパティには、複数の呼び出しを対象としたプロパティが含まれています。各リクエストでこれらの値をリセットする必要があると、ポイントが無効になります。
HttpClient
のもう1つの主な利点は、HttpMessageHandlers
を要求/応答パイプラインに追加して、横断的な関心事を適用できることです。これらは、ロギング、監査、スロットル、リダイレクト処理、オフライン処理、メトリックのキャプチャ用です。あらゆる種類の異なるもの。各リクエストで新しいHttpClientが作成される場合、これらのメッセージハンドラーはすべて各リクエストで設定する必要があり、何らかの方法でこれらのハンドラーのリクエスト間で共有されるアプリケーションレベルの状態も提供する必要があります。
HttpClient
の機能を使用すればするほど、既存のインスタンスを再利用する意味があることがわかります。
しかし、最大の問題は、私の意見では、HttpClient
クラスが破棄されると、HttpClientHandler
を破棄し、ServicePointManager
によって管理される接続のプール内のTCP/IP
接続を強制的に閉じることです。これは、新しいHttpClient
を含む各リクエストでは、新しいTCP/IP
接続を再確立する必要があることを意味します。
私のテストでは、LAN上でプレーンHTTPを使用しているため、パフォーマンスヒットはごくわずかです。これは、HttpClientHandler
が接続を閉じようとしても接続を開いたままにしているTCPキープアライブが存在するためだと思われます。
インターネット経由でのリクエストについては、別の話を見ました。毎回リクエストを再度開く必要があるため、パフォーマンスが40%低下しました。
HTTPS
接続のヒットはさらに悪化すると思われます。
私のアドバイスは、アプリケーションの存続期間中はHttpClientのインスタンスを保持する接続する個別のAPIごとです。
アプリケーションを拡張したい場合、違いは非常に大きいです!負荷に応じて、非常に異なるパフォーマンス値が表示されます。 Darrel Millerが言及しているように、HttpClientはリクエスト間で再利用されるように設計されています。これは、それを書いたBCLチームのメンバーによって確認されました。
私が最近行ったプロジェクトは、非常に大規模で有名なオンラインコンピューター販売店が、新しいシステムのブラックフライデー/休日のトラフィックをスケールアウトできるようにすることでした。 HttpClientの使用に関するパフォーマンスの問題に遭遇しました。 IDisposable
を実装しているため、開発者はインスタンスを作成し、それをusing()
ステートメント内に配置することで、通常行うことを行いました。負荷テストを開始すると、アプリはサーバーをひざまずかせました-はい、サーバーはアプリだけではありません。その理由は、HttpClientのすべてのインスタンスがサーバー上のポートを開くためです。 GCの非決定的なファイナライズと、複数の OSIレイヤー にまたがるコンピューターリソースで作業しているという事実のため、ネットワークポートを閉じるには時間がかかる場合があります。実際、Windows OSitselfは、ポートを閉じるのに最大20秒かかります(Microsoftによる)。閉じられるよりも速くポートを開いていました-サーバーポートの枯渇によりCPUが100%に打撃を受けました。私の修正は、HttpClientを静的インスタンスに変更して問題を解決することでした。はい、それは使い捨てのリソースですが、オーバーヘッドはパフォーマンスの違いによって圧倒的に大きくなります。負荷テストを行って、アプリの動作を確認することをお勧めします。
この呼び出しに特に注意してください。
HttpClientは、一度インスタンス化され、アプリケーションのライフサイクルを通じて再利用されることを目的としています。特にサーバーアプリケーションでは、すべてのリクエストに対して新しいHttpClientインスタンスを作成すると、高負荷の下で利用可能なソケットの数を使い果たします。これにより、SocketExceptionエラーが発生します。
異なるヘッダー、ベースアドレスなどで静的なHttpClient
を使用する必要がある場合は、HttpRequestMessage
を手動で作成し、HttpRequestMessage
にそれらの値を設定する必要があります。次に、HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)
を使用します
他の回答が示すように、HttpClient
は再利用を目的としています。ただし、マルチスレッドアプリケーション全体で単一のHttpClient
インスタンスを再利用するということは、BaseAddress
やDefaultRequestHeaders
などのステートフルプロパティの値を変更できないことを意味します(したがって、アプリケーション全体で一定である場合にのみ使用できます)。
この制限を回避するための1つの方法は、必要なHttpClient
メソッド(HttpClient
、GetAsync
など)をすべて複製するクラスでPostAsync
をラップし、それらをシングルトンHttpClient
に委任することです。しかし、それはかなり退屈です( 拡張メソッド もラップする必要があります)、幸いなことに 別の方法があります -新しいHttpClient
インスタンスを作成し続けますが、基になるHttpClientHandler
を再利用します。ハンドラーを破棄しないでください:
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)
}
};
}
大量のWebサイトに関連していますが、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;
「このプロパティを使用して、ServicePointオブジェクトのアクティブな接続が無期限に開いたままにならないようにすることができます。このプロパティは、負荷分散シナリオなど、接続を定期的に切断および再確立するシナリオ向けです。
既定では、要求のKeepAliveがtrueの場合、MaxIdleTimeプロパティは、非アクティブのためにServicePoint接続を閉じるためのタイムアウトを設定します。 ServicePointにアクティブな接続がある場合、MaxIdleTimeは効果がなく、接続は無期限に開いたままになります。
ConnectionLeaseTimeoutプロパティが-1以外の値に設定され、指定された時間が経過した後、アクティブなServicePoint接続は、そのリクエストでKeepAliveをfalseに設定することによりリクエストを処理した後に閉じられます。この値を設定すると、ServicePointオブジェクトによって管理されるすべての接続に影響します。」
フェールオーバーするCDNまたはその他のエンドポイントの背後にサービスがある場合、この設定は、発信者が新しい宛先に追従するのに役立ちます。この例では、フェイルオーバーの60秒後にすべての発信者が新しいエンドポイントに再接続する必要があります。依存サービス(呼び出すサービス)とそのエンドポイントを知っている必要があります。
Simon Timmsによるこのブログ投稿も参照してください。 https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
ただし、
HttpClient
は異なります。IDisposable
インターフェイスを実装しますが、実際には共有オブジェクトです。これは、カバーの下ではリエントラントであり、スレッドセーフであることを意味します。実行ごとにHttpClient
の新しいインスタンスを作成する代わりに、アプリケーションのライフタイム全体でHttpClient
の単一のインスタンスを共有する必要があります。理由を見てみましょう。
指摘する1つのことは、「使用しない」ブログの注意事項のいずれも、考慮する必要があるのはBaseAddressとDefaultHeaderだけではないということです。 HttpClientを静的にすると、リクエスト間で伝達される内部状態が存在します。例:HttpClientを使用してFedAuthトークンを取得する(OAuth/OWIN/etcを使用しない理由を無視して)サードパーティに対して認証を行い、その応答メッセージにはFedAuthのSet-Cookieヘッダーがあり、これはHttpClient状態に追加されます。 APIにログインする次のユーザーは、リクエストごとにこれらのCookieを管理していない限り、最後の人のFedAuth Cookieを送信します。