LoginSignup
10
3

More than 1 year has passed since last update.

Apache HttpClient 5.x 再利用(使いまわし) 接続設定 ポイント まとめ

Posted at

概要

Apache HttpClient 5.x を使うときのポイントをまとめる。以下の公式ドキュメントはあっさりしているので、コードを読むしかなかった...どこかに詳細があるんだとは思いつつ見つけられなかったのでメモを残す。
また、 Apache HttpClient 4.x と仕組みは似ているので、そのあたりの記事も参考にした。

Apache HttpComponents – HttpClient Overview

前提として、PoolingHttpClientConnectionManagerのコードで得た知識なので、他のManagerと設定値の扱いが若干ことなるかもしれない。現時点の最新のライブラリのバージョンは、5.1.3だが、読んだコードはもう少し進んでいるのでその辺の差異も注意したい。

参考

Apache HttpClient 4.x 系であれば上の記事がとても参考になる

ポイント

  • http client インスタンスは使い回す
  • EntityUtils.consume を使ってレスポンスボディを確実に消費(使い切る)する
  • maxConnTotal, maxConnPerRouteを設定する
  • 必要に応じて timeToLive(永続化の最大期間)を設定する

詳細

http client インスタンスは使い回す

基本はこれ。簡単なサンプルコードでは、リクエストごとにインスタンスを生成するように(みえるように)書かれているが、コネクションの再利用するために http client のインスタンスを使い回すことが必要になる

メリット

  • http client の生成にかかる CPU やメモリの節約になる(リクエスト量にもよるが割と変わってくるので試してほしい)
  • コネクション取得にかかる時間が節約できる (特にSSL(https)通信の場合は数十msの違いがでる)

注意点

  • 利用が終わったら client.close() を用いて接続を切る

HttpClient Quick Startでは、1インスタンスで2回リクエストを呼ぶようなサンプルにはなっているので、ぎりぎり意図は感じることができる

また、サンプルコードのコメントに接続の再利用に対する注意が書かれている(次のポイントとなる EntityUtils.consumeにも関連する)

The underlying HTTP connection is still held by the response object to allow the response content to be streamed directly from the network socket. In order to ensure correct deallocation of system resources the user MUST call CloseableHttpResponse#close() from a finally clause. Please note that if response content is not fully consumed the underlying connection cannot be safely re-used and will be shut down and discarded by the connection manager.

EntityUtils.consume を使ってレスポンスボディを確実に消費(使い切る)する

http client のインスタンスを使いまわしているのに、コネクションの取得時間が改善されない場合はこれが原因の可能性がある。コネクションの再利用には、リクエストで使い終わったあとに、レスポンスボディを使い切ってゴミ?が残らないようにしなければいけない(でなければ、コネクションは切断される)。

EntityUtils.consume を使う方法

getEntity でレスポンスボディを取得したあとに、EntityUtils.consumeを呼び出す

Quick Startより引用
try (CloseableHttpResponse response1 = httpclient.execute(httpGet)) {
    HttpEntity entity1 = response1.getEntity();
    // ここで処理する
    EntityUtils.consume(entity1);
}

CloseableHttpClientの実装を見ると、例外発生時もリクエストボディを消費している。例外発生時もコネクションを切断しなくない場合は、以下のようにするとよさそう

CloseableHttpClientの実装を参考
try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
    try {
        final HttpEntity entity = response.getEntity();
        // ここで処理する
        EntityUtils.consume(entity);
    } catch (final HttpException t) {
        final HttpEntity entity = response.getEntity();
        try {
            EntityUtils.consume(entity);
        } catch (final Exception t2) {
            LOG.warn("Error consuming content after an exception.", t2);
        }
        throw new ClientProtocolException(t);
    }
}

HttpClientResponseHandler を使う方法

HttpClientResponseHandlerを使ってレスポンスを処理する場合は、executeメソッド内でEntityUtils.consumeを呼んでいるため、リクエストボディを個別に消費するコードが不要になる。HttpClientResponseHandlerは、別クラスで定義してもよいし、無名クラスで定義してもよいが、別クラスで定義する場合は、リクエストごとにインスタンスを生成する必要はないので注意する(しても大してかわらないかもしれない...不明)

Migration from Apache HttpClient 4.x APIsより引用
JsonNode responseData = client.execute(httpPost, response -> {
    if (response.getStatusLine().getStatusCode() >= 300) {
        throw new ClientProtocolException(Objects.toString(response.getStatusLine()));
    }
    final HttpEntity responseEntity = response.getEntity();
    if (responseEntity == null) {
        return null;
    }
    try (InputStream inputStream = responseEntity.getContent()) {
        return objectMapper.readTree(inputStream);
    }
});

maxConnTotal, maxConnPerRoute を設定する

PoolingHttpClientConnectionManagerのデフォルトでは、maxConnTotalは25、maxConnPerRouteは5に設定されている。それぞれの設定の意味は以下のとおり。
このデフォルト値で足りない場合にConnectionRequestTimeoutExceptionが出るが、十分にコネクションをプールしておかないとコネクション取得に時間がかかってしまう問題がある。

項目 意味 注意
maxConnTotal コネクションプールの最大コネクション数 PoolConcurrencyPolicyがLAXの場合は無効
maxConnPerRoute 宛先(ホストとポート番号(80,443など)とスキーマ(http or https))単位の最大のコネクション数

必要に応じて timeToLive(永続化の最大期間)を設定する

timeToLiveはコネクションの永続化の最大期間で、コネクションが作成された時間から切断されるまでの最長の期間を設定できる。例えば、keepaliveの設定でコネクションが維持された場合も、このtimeToLiveを設定しておけば、コネクションの維持期間を設定できる(もちろん、無限にも設定できる)。
コネクションを定期的に再接続させることで、コネクションの接続維持中に接続先のホストでコネクションが意図せず破棄されるなどのケースで、通信に失敗することを抑制できる

この設定は、コネクションプールからコネクションを取得したときに毎回チェックされている(少なくとも最新の実装では...)が、これとは別にHttpClientBuilderevictExpiredConnectionsevictIdleConnectionsを設定している場合は、定期的(デフォルト5秒)でtimeToLiveのチェックをコネクションプールのコネクションに実施してくれる(maxIdleTime指定時は、アイドル状態のコネクションの破棄もしてくれる)。こうすることでコネクションプールからコネクションを取り出したときのチェックや再取得のコストが減るということらしい。レイテンシの要件が厳しいアプリケーションでは有効かもしれない。

その他の時間に関する設定値

ConnectionConfig

項目 意味 説明
connectTimeout コネクション確立時のタイムアウト値 RequestConfigのconnectTimeoutよりも優先される模様
socketTimeout ソケットタイムアウト コネクション確立時に使用されるようだが、デフォルト値としてレスポンス受付時のソケットタイムアウト値としても利用されるんだろうな
validateAfterInactivity コネクションが未使用になったあとに、コネクションが利用できるかをチェックするまでの期間 今のPoolingHttpClientConnectionManagerの実装だとデフォルト値が3秒になっているので、短すぎる場合は長めに設定するのもよさそう。ただし、どこまで性能に寄与するかは謎。この値にマイナス値を指定するとこのチェックは行われないので、リトライ処理などが実装されていて、多少の接続不良が許容できるならそれでもいいのかもしれない。
timeToLive 永続化の最大期間 上の章で説明済

RequestConfig

項目 意味 説明
connectionRequestTimeout コネクション取得時のタイムアウト値 コネクションプールからコネクションを取り出すときにこのタイムアウト値を超えるとエラーになる(デフォルトは3分)
connectTimeout コネクション確立時のタイムアウト値 connectionRequestTimeoutの方を長く取らないと辻褄が合わなくなる。connectionConfigのconnectTimeoutが優先されるようです(未検証)
responseTimeout レスポンスを受け取るときのソケットタイムアウト値 完全にレスポンスを受け取るまでの時間のように見えるが、接続先のソケットタイムアウトに設定している。これを指定しない場合は、connectionConfigのsocketTimeoutが有効になっているんだろうな
connectionKeepAlive http keep alive タイムアウト コネクションがアイドル状態になったあとに維持される最大期間
10
3
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
10
3