LoginSignup
20
22

More than 5 years have passed since last update.

【.NET】HttpClient と接続プールの振る舞い

Last updated at Posted at 2017-11-24

ちょっと長くなってしまったので、先に結論。

HttpClient は Dispose されると接続プールも破棄されます。
なので HttpClient インスタンスは可能な限り使い回して、接続プールの恩恵を最大化しましょう。
主要メソッドはスレッドセーフなので、マルチスレッドでも安心して使えます。(公式リファレンスの受け売り感)

staticフィールドに保持する例
// https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.httpclient?view=netframework-4.7.1#Remarks
public class GoodController : ApiController {
    private static readonly HttpClient _httpClient;

    static GoodController() {
        _httpClient = new HttpClient();
    }
}

static 化による DNS 問題は下記。


本稿では HttpWebRequest / HttpClient の接続プールに関する特性と、それに沿った HttpClient の効果的な使用法を示していきます。

HttpWebRequest の特性

  • HttpWebRequest は内部で ServicePoint の接続グループによる接続プーリングが行われている。

So as soon as your application invoke the GetResponse() method there are number things happened. Following is the sequence of operations took place.

  • a) System.Net see it is a first request, it creates a connection group for it,
  • b) Since this is new connection group, it also creates a servicepoint on this group (for the sack of simplicity, assume that there is no authentication involved and servicepoint is on per host basis),
  • c) It creates a new connection and open a new socket connection to remote host,

...
Second request would be send on the same connection as previous request, no new connection is created.

Understanding System.Net Connection Management and ServicepointManager – Adarsh's blog

HttpClient の特性

  • HttpClient はインスタンス毎に独立した接続プーリングを行っている。

    In addition, every HttpClient instance uses its own connection pool, isolating its requests from requests executed by other HttpClient instances.

    HttpClient Class (System.Net.Http) | Microsoft Docs

    • HttpWebRequest でいうところの接続グループに近い。
  • 接続のライフサイクルは下記によって制御されている。

  • HttpClient の主要メソッドはスレッドセーフである。

    The following methods are thread safe:
    1. CancelPendingRequests
    2. DeleteAsync
    3. GetAsync
    4. GetByteArrayAsync
    5. GetStreamAsync
    6. GetStringAsync
    7. PostAsync
    8. PutAsync
    9. SendAsync

    HttpClient Class (System.Net.Http) | Microsoft Docs

HttpClient のアンチパターンと良いパターン

× IDisposable な HttpClient の良くある使用法

public class GoodController : ApiController {
    public string GetHello() {
        ...
        using (var client = new HttpClient()) {
            var res = await client.GetAsync(...);
            ...
        }
    }
}

リクエストの度に HttpClient を生成/破棄してしまうと、TIME_WAIT な接続だらけになってしまい、ソケットを使い潰してしまうので NG。

In a web application, this technique is not scalable. A new HttpClient object is created for each user request. Under heavy load, the web server may exhaust the number of available sockets, resulting in SocketException errors.

Improper Instantiation antipattern | Microsoft Docs
architecture-center/index.md at master · mspnp/architecture-center

○ HttpClient の効果的な使い方

public class GoodController : ApiController {
    private static readonly HttpClient _httpClient;

    static GoodController() {
        _httpClient = new HttpClient();
    }

    public string GetHello() {
        ...
        var res = await _httpClient.GetAsync(...);
        ...
    }
}

HttpClient はシングルトンにしてアプリケーションとライフサイクルを合わせる事で、コネクションの再利用を最大化する事が基本となります。
これによって、接続プールのライフサイクルを HttpWebRequest の頃と(ほぼ)同様にする事ができます。

Note
HttpClient is intended to be instantiated once and re-used throughout the life of an application. Especially in server applications, creating a new HttpClient instance for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.

Calling a Web API From a .NET Client (C#) | Microsoft Docs

ただし、セキュリティ要件等によっては、接続グループを分けるように HttpClient インスタンスを複数運用する事が望ましいケースもあります。

ロードバランサーを経由する接続では、最適な接続先(ホスト)が時間と共に変わる可能性がありますので、定期的な再接続が必要になるかも知れません。

However some services might actually ask you to close the connection after each request. In particular if your request goes through a load balancer by keeping the connection alive your requests will not go through the load balancer so you might end up getting throttled.

Best practices for using HttpClient on Services – Sharad Cornejo Altuzar's blog

重要な事は、シングルトンか否かではなく、貴重なリソース(ソケット)を無為に使い潰さないよう配慮する事でしょう。

余談: HttpClient を破棄しないと DNS の変更が反映されない件

さて、いざ再利用をしてみると問題があるそうです。

しかし、深刻な問題があるのがわかりました。DNSの変更が反映されず、HttpClientは(HttpClientHandlerを通じて)ソケットがクローズするまでコネクションを使い続けます。無制限にです。

開発者を苦しめる.NETのHttpClientのバグと紛らわしいドキュメント - InfoQ

原文では続けて下記のように分析しています。

HttpClientHandler creates a connection group (named with its hashcode) and does not close the connections in the group until getting disposed. This basically means the DNS check never happens as long as a connection is open.

Byte Rot: Singleton HttpClient? Beware of this serious behaviour and how to fix it

HttpClient のライフサイクル中は接続がプールされている為、接続が破棄されない限り DNS の変更が反映されないのはその通りだと思います。

ただ HttpWebRequest の頃も同様に、MaxIdleTimeConnectionLeaseTime によるタイムアウト、 または CloseConnectionGroup() をしない限り接続はプールされ続けますので、この件は HttpClient とは無関係に存在する課題です。

言い換えれば、氏の ConnectionLeaseTime による解決策は、レガシーな HttpWebRequest を使用したコードにも通用する方法といえるでしょう。

上手くいかないケースも

バックエンドで ServicePoint使用していない実装ではこの解決策が通用しません。

そもそも管理下にあるオブジェクト(HttpClient)が裏側で与り知らぬオブジェクト(ServicePoint / ServicePointManager)に依存している かも知れない 事を正しく認識しなくてはならない状況は、System.Web の呪縛1と同様に辛いものがあります。

個人的には HttpClient インスタンスの破棄と再作成で対応する方がシンプルで良いと思います。

See Also


  1. 超ビッグなインスタンス System.Web.HttpContext.Current がそこかしこから参照されている状況。依存関係が見え辛くなり、次第にコードがスパゲッティ化していく。 

20
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
22