apache HttpClient の eviction について

この記事の目的

apache HttpClinent の eviction について コードリーティングした内容のメモ。 コネクションの有効期限(expire?)についても書いているがプールから取り除く処理(eviction?) について調べるときに必要だったので書いている。混同してたらごめんなさい。動作確認してたりしてなかったりするので鵜呑みにしないように。

対象

以下 http components を hc と略していたりする。文言が統一されてないのは殴り書きなので。

内容

概念

抽象クラスや正確な記述などはすっとばして理解の範囲で記載

f:id:nise_nabe:20161229123419p:plain

関連クラスやメンバ

  • org.apache.http.impl.client.HttpClientBuilder
    • connManagerShared
    • evictExpiredConnections
    • evictIdleConnections
    • maxIdleTime, maxIdleTimeUnit
    • connTimeToLive, connTimeToLiveTimeUnit
  • org.apache.http.impl.client.IdleConnectionEvictor
  • org.apache.http.impl.conn.CPool
  • org.apache.http.impl.conn.CPoolEntry
  • org.apache.http.pool.PoolEntry
    • validityDeadline
    • expiry
  • org.apache.http.impl.conn.PoolingHttpClientConnectionManager
    • timeToLive

メモ内容

概念としては以下の二つ

  • プールされたコネクションの有効期間 -> PoolingHttpConnectionManager (あるいは HttpClientBuilder)に設定
    • 作成されてからの有効期間 -> 設定可能
    • 最後に使用されてからの有効期間 -> 基本的に HTTP/1.1 の keepalive
  • プールされたコネクションが生存期間内かどうかチェックする間隔 -> HttpClientBuilder に設定

IdleConnectionEvictor のコンストラクタでスレッドを立てて、一定時間ごとに失効したコネクションあるいはアイドル時間が一定時間を超過したコネクションをクローズする。

  1. 設定する利点は?
  2. 設定しない場合有効期限が切れたコネクションが Pool に残ったままになるので、定期的に掃除できれば取得時に余計な探索をするコストを減らせる。

失効の判定設定は PoolingHttpConnectionManager の timeToLive を設定する。 設定場所は HttpClientBuilder の使い方によって以下の二つのどちらになる。

  • HttpClientBuilder で各種設定をする
  • HttpClientBuilder#setConnectionManager() に PoolingHttpClientConnectionManager を設定する。
    • こちらを使う場合はこちらが優先される

自分で timeToLive を指定せずに生成した場合は -1 となる。これは CPool で無限(正確には Long.MAX_VALUE)として扱われる。

PoolingHttpClientConnectionManager のコンストラクタに timeToLive を指定する。この中で Pool インタフェースの実装である CPool が生成され、CPoolEntry が生成されるたびに生成時刻から expire を計算する。 そのため HttpClientBuilder に自身で生成したものを設定した時、このあたりの挙動が変化する可能性があるので注意。

失効の判定設定は HttpClientBuilder の evictExpiredConnections() または evictIdleConnections() でおこなう。 チェック間隔は evictExpiredConnections() の場合は 10 秒、間隔を自分で設定したい場合は evictIdleConnections() を使う。


スレッドは基本は HttpClient 毎に生成される。正確には HttpClientBuilder#build() されるたびに生成される。 ただし ConnectionManager を共有する場合は行われない。

  1. ConnectionManager を共有するユースケースは?
  2. わからん。 Pooling を共有する場合はアウトゴーイングなコネクションで消費するローカルポートを hc 全体で管理したい場合に使えば良いのかも。この場合コネクションのシャットダウンもちゃんと管理しないとダメそう。 管理しないとっていうのは ConnectionManager が shared になってる場合は CloseableHttpClient を close してもプールされたコネクションはクローズされない。(そりゃ他の CloseableHttpClient で使ってる可能性があるのに勝手にクローズされちゃこまるよねと)なので自分で ConnectionManager の close を使う必要がありそう。

HttpClient では Closeable のリストを管理しており、各 HttpClient で close() 時動作を追加することが出来る。この設計を使って IdleConnetionEvictor で生成されたスレッドを終了させる。

コード(動作確認など)

基本的な使い方

    // 特に制限なしなので繋いだら Connection ヘッダに従う。 Keep-Alive の場合は繋ぎっぱなし。
    CloseableHttpClient client = HttpClientBuilder.create().build();

    // 10 分後に使用不能になる。
    // keepalive があれば min(10分, 生存期間 + keepalive 時間) に使用不能になる。
    // 取得時に Pool からの取得時に使えないものとして取得し直す
    CloseableHttpClient client = HttpClientBuilder.create()
                                         .setConnectionTimeToLive(10, TimeUnit.MINUTES)
                                         .build();

    // 条件は前項と一緒。
    // 10 秒ごとにチェックしてプールから取り除く。取得時のコネクション取得コストを軽減できる可能性がある。
    CloseableHttpClient client = HttpClientBuilder.create()
                                         .setConnectionTimeToLive(10, TimeUnit.MINUTES)
                                         .evictExpiredConnections()
                                         .build();

    // 10 分ごとにチェックする
    // 基本的にリクエスト先で keep alive で時間が設定されている場合に意味がある
    CloseableHttpClient client = HttpClientBuilder.create()
                                         .evictIdleConnections(10, TimeUnit.MINUTES)
                                         .build();

    // 10 分ごとにチェックする(evictExpiredConnections() は意味ない)
    CloseableHttpClient client = HttpClientBuilder.create()
                                         .evictExpiredConnections()
                                         .evictIdleConnections(10, TimeUnit.MINUTES)
                                         .build();

以下動作確認

手っ取り早く Spring Boot で Bean 登録して確認する。

確認:本当にスレッドは生成されるのか?

確認方法: evictExpiredConnections() を呼ぶ

    @Bean
    public HttpClient httpClient() {
        return HttpClientBuilder.create().evictExpiredConnections().build();
    }

jconsole で起動したアプリを選択してスレッドを見て「Connection Evictor」でフィルタする。

f:id:nise_nabe:20161229133137p:plain

確認:Connection Evictor スレッドがリークする可能性がある?

動作確認: HttpClient を close() せずに何度も build() してみる。

    @Bean
    public HttpClient httpClient() {
        HttpClientBuilder.create().evictExpiredConnections().build();
        HttpClientBuilder.create().evictExpiredConnections().build();
        HttpClientBuilder.create().evictExpiredConnections().build();
        return null;
    }

スレッド起動したままだった。

f:id:nise_nabe:20161229133243p:plain

ちゃんと close() するとスレッドはなくなっている。

    @Bean
    public HttpClient httpClient() throws IOException {
        HttpClientBuilder.create().evictExpiredConnections().build().close();
        HttpClientBuilder.create().evictExpiredConnections().build().close();
        HttpClientBuilder.create().evictExpiredConnections().build().close();
        return null;
    }

f:id:nise_nabe:20161229160323p:plain