非常に高速に動作するRESTfulサービスがあります。ローカルホストでテストしています。クライアントはSpring RESTテンプレートを使用しています。私は単純なアプローチを使用して開始しました。
RestTemplate restTemplate = new RestTemplate(Collections.singletonList(new GsonHttpMessageConverter()));
Result result = restTemplate.postForObject(url, payload, Result.class);
これらのリクエストを多数行うと、次の例外が発生します。
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8080/myservice":No buffer space available (maximum connections reached?): connect; nested exception is Java.net.SocketException: No buffer space available (maximum connections reached?): connect
これは、接続が閉じられておらず、TIME_WAIT状態でハングしていることが原因です。一時ポートが使い果たされると、例外が発生し始めます。その後、実行はポートが再び空くのを待ちます。長い休憩で最高のパフォーマンスを見ています。私が得ている速度はほとんど必要なものですが、もちろん、これらのTIME_WAIT接続は良くありません。 Linux(Ubuntu 14)とWindows(7)の両方でテストしました。ポートの範囲が異なるため、異なる時点で同様の結果が得られました。
これを修正するために、Apache Http ComponentsライブラリのHttpClientBuilderでHttpClientを使用してみました。
RestTemplate restTemplate = new RestTemplate(Collections.singletonList(new GsonHttpMessageConverter()));
HttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(TOTAL)
.setMaxConnPerRoute(PER_ROUTE)
.build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
Result result = restTemplate.postForObject(url, payload, Result.class);
このクライアントでは、例外はありません。クライアントは現在、非常に限られた数の一時ポートのみを使用しています。しかし、使用する設定(TOTALおよびPER_ROUTE)に関係なく、必要なパフォーマンスを得ることができません。
netstat
コマンドを使用すると、サーバーへの接続があまり多くないことがわかります。数千に設定しようとしましたが、クライアントはそれほど多く使用しないようです。
あまり多くの接続を開かずに、パフォーマンスを改善するためにできることはありますか?
更新:合計およびルートごとの接続数を5000および2500に設定しようとしましたが、クライアントが100以上を作成していないようです(netstat -n | wc -l
)。 RESTサービスはJAX-RSを使用して実装され、Jettyで実行されます。
更新2:いくつかのメモリ設定でサーバーを調整しましたが、本当に良いスループットが得られています。素朴なアプローチはまだ少し高速ですが、クライアント側のプーリングのほんの少しのオーバーヘッドだと思います。
実際、Spring Bootは接続をリークしていません。ここで見ているのは、Linuxカーネル(およびすべての主要なOS)の標準的な動作です。マシンから閉じられたすべてのソケットは、しばらくの間TIME_WAIT
状態になります。これは、その一時ポートを使用する次のソケットが、そのポートの前のソケットに実際に向けられたパケットを受信しないようにするためです。 2つの間に見られる違いは、それぞれが行う接続プーリングアプローチの結果です。
具体的には、RestTemplate
しないデフォルトで接続プーリングを使用します。これは、すべての休憩呼び出しが新しいローカル一時ポートとサーバーへの新しい接続を開くことを意味します。サービスが非常に高速の場合、利用可能なローカルポート範囲をすぐに吹き飛ばします。 Apache HttpClient
を使用すると、接続プーリングを利用できます。これにより、アプリケーションで説明した問題が表示されなくなります。ただし、LinuxカーネルがTIME_WAIT
からソケットを取得するよりもサービスの応答が速い場合、接続プーリングにより、何をしてもクライアントの速度が低下します(速度が低下しなければ-再びローカルの一時ポートが不足します)。
LinuxカーネルでTCP再利用を有効にすることは可能ですが、危険になる可能性があります(パケットが遅延する可能性があり、理解できないランダムなパケットを受信するエフェメラルポートがあらゆる種類の原因となる可能性があります問題)。ここでの解決策は、2番目の例と同じように接続プーリングを使用し、十分な数を使用して、目的のパフォーマンスに近い値を達成することです。
接続プールを調整しやすくするために、maxConnPerRoute
およびmaxConnTotal
パラメーターを調整する必要があります。 maxConnPerRoute
は、単一のIP:Portペアに対して行われる接続の数を制限し、maxTotal
は、これまでに開かれる接続の総数を制限します。あなたの場合、すべてのリクエストは同じ場所に行われているように見えるため、同じ(高い)値に設定できます。