FC2ブログ

水雲風

とりあえず人生を記録と妄想の吐き出しの地

Jakarta Commons HttpClientでのTCPコネクション(2008/04/01)

仕事でサーバの負荷テストのため、ツールを作成し多数のスレッドからHTTPリクエストを送ってみています。
ツールはJavaを使って作成していて、リクエストの送信にはJakarta commons HttpClientを使っています。

ツールを使用中にnetstatコマンドを使って、状態を見ると多数のTIME_WAIT状態のコネクションが発生しています。
ツールを使っていくと、空きポートがなくなり通信エラーが発生するようになります。
Windowsのデフォルト値では5000ポートまでしか接続できないので、レジストリをいじってポート数を増やすとなんとか通信エラーは発生しません。

上記の現象がツールのバグなのか正常ケースなのかわからないために調べてみました。
ここでは、その調査結果を記録します。
結論を言えば、TCPの仕様のようです。


TCPのコネクションについては以下のサイトがよくわかりました。
3 Minutes Networking No.39

また、まさに上記の現象について説明している英語サイトを見つけました。
英語は苦手なので翻訳した結果を以下にメモしておきます。
内容や訳が間違っているところがあるかと思います。

以下の文書は、http://wiki.apache.org/jakarta-httpclient/FrequentlyAskedConnectionManagementQuestionsの内容の一部を翻訳したものです。

TIME_WAIT状態のコネクション

TIME_WAIT状態

アプリケーションを走らせた後に、netstatコマンドを使用します。
すると、たくさんのTIME_WAIT状態が見つかります。
ここで、あなたはなぜこれらのコネクションがクリーンアップされないか疑問を持つでしょう。

TIME_WIAT状態とは何か

TIME_WAIT状態は、TCPの保護機能です。
ソケットを閉じる側は一般的に1~4分TIME_WAIT状態でコネクションを保ちます。
これはコネクションを閉じた後に起こり、クリーンアップの問題ではありません。
TIME_WAIT状態はデータの損失と破損を防ぎます。
技術的な詳細は、Unix Socket FAQ, section 2.7を参照して下さい。

どのコネクションがTIME_WAITになって、他がならないか

もし、コネクションがアプリケーションによって正しく閉じられるなら、TIME_WAIT状態になります。
コネクションがサーバで正しく閉じられるなら、サーバはTIME_WAIT状態を保ちクライアントはそうではありません。
結合がリセットされるか、正しくないやり方でアプリケーションが落とされるなら、TIME_WAIT状態とはなりません。
残念なことに、コネクションが正しく閉じられるかどうかは、あなたにとって必ずしも明らかではありません。
これはコネクションがプールされてデフォルトで再利用のために開いておかれるからです。
HttpClient 3.x、HttpClient 4、さらに標準的なJava HttpURLConnetionはそうします。
大部分のアプリケーションは単にリクエストを実行し、それからレスポンスのストリームから読み込みます。
そして、最後にストリームを閉じます。
レスポンスのストリームを閉じることは、コネクションを閉じることと同じではありません。
レスポンスのストリームを閉じると、プールにコネクションを返します。
しかし、それは可能ならば開いておかれます。
別のリクエストを同じホストに2、3秒(分)以内に送るなら、これは多くの時間を節約します。

コネクションプールにはコネクション数の限界があります。
プールは5、または100、もしくは1つだけのコネクションを持つかもしれません。
ホストにリクエストを送信し、ホストへの開いたコネクションがプールにない時、新しいコネクションを開く必要があります。
しかし、もしプールが既にいっぱいである場合は、新しいコネクションが開かれる前に、開いたコネクションは閉じられなければいけません。
この場合は、古いコネクションは正しく閉じられ、TIME_WAIT状態となります。

アプリケーションが終了してJVMが終わるとき、プールの開いたコネクションは正しく閉じられないでしょう。
それらはTIME_WAIT状態になることなく、リセットかキャンセルされます。
これを避けるために、アプリケーションが使っているコネクションプールのshutdownメソッドを終了する前に呼ばなければいけません。
標準的なHttpURLConnectionは、コネクションプールをshutdownする公開メソッドがありません。

ポート数の使い果たし

アプリケーションには、たくさんのコネクションを短時間で開いて閉じるものがあります。
例えばサーバの負荷テストする時などです。
TIME_WAIT状態でコネクションは、そのポート番号が他のコネクションのために再利用されることを防ぎます。 
これはエラーではありません。
TIME_WAITの目的です。

TCPは、JAVAを通してではなくOSレベルで設定されます。
最初の行動としては、マシンでの短命なポート数の増加するべきです。
特にWindowsは短命なポート数において低いデフォルト値を持ちます。
PerformanceWikiには、一般的なOSチューニングのTIPSがあります。
それぞれにのTIPSにはNetworkのセクションがあります。

短命なポート数を増やすことが問題を解決しない時だけ、TIME_WAIT状態の持続時間を減らすことを考えるべきです。
おそらく一般的に2度の遅延時間を考慮にいれた時間であるTIME_WAITの持続時間として、IPパケットの最大有効期間を減らさなければならないでしょう。
これはマシンで動いている全てのアプリケーションに影響を及ぼすことに注意して下さい。
方法を私たちに聞かないでください。私たちはネットワークチューニングの専門家ではありません。

アプリケーションレベルで問題を扱う若干の方法があります。
1つの方法は、各リクエストで「Connection: close」ヘッダを送ることです。
それはサーバにコネクションを閉じるように言うので、向こう側でTIME_WAIT状態になります。
もちろんこれはプールしているコネクションのkeep-aliveの特徴を無効にし、それによってパフォーマンスを低下させます。
もしサーバに対して負荷テストをしているなら、アプリケーションの一般的でない動作は、試験結果をゆがめるかもしれません。

別の方法はコネクションを正しく閉じないことです。
コネクションを正しく閉じる代わりにリセットさせる特別な値としてSO_LINGERを設定する方法があります。
HttpClient APIは直接これをサポートしていないことに注意して下さい。
このハックを実装するためにいくつかのクラスを拡張や修正する必要があります。

さらに別の方法は、TIME_WAITのコネクションによってまだふさがれているポートを再利用することです。
ソケットを開くとき、SO_REUSEADDRオプションを指定することによって可能です。
Java1.4はこの目的のためにSocket.setReuseAddressメソッドを導入しました。
これでも、HttpClientのクラスを拡張・修正する必要があります。
しかし、少なくともそれはハックではありません。
スポンサーサイト



  1. 2008/04/01(火) 19:08:01|
  2. 計算機
  3. | トラックバック:1
  4. | コメント:0
<<映画「ベルリン天使の詩」 | ホーム | 浦富海岸 - 竜神洞>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバックURLはこちら
http://mechagappa.blog14.fc2.com/tb.php/547-2e64936a
この記事にトラックバックする(FC2ブログユーザー)

X|X ?T

X?WqŔYł?BX?WqŔYł?B?G??Ă?A...
  1. 2008/04/02(水) 02:21:15 |
  2. ?

プロフィール

おげれつ脱脂綿

カレンダー

01 | 2020/02 | 03
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29

カテゴリー

月別アーカイブ(タブ)

最近の記事

最近のコメント

最近のトラックバック

ブロとも申請フォーム

この人とブロともになる

ブログ内検索

RSSフィード

全ての記事を表示する

全ての記事を表示する