10

解放しません:PoolingHttpClientConnectionManagerは、私は次のことを達成するために春を使用しています接続

をサーバー上に、私は、XML形式でRESTインターフェースを介してデータを受け取ります。データをJSONに変換し、別のサーバーにPOSTしたいと思っています。私のコードは、(私は私の雇用者の怒りを避けるために、いくつかの敏感なクラス名/ URLの削除)次のようになります。

メイン/ Configurationクラス:

package stateservice; 

import org.apache.http.HttpHost; 
import org.apache.http.client.config.RequestConfig; 
import org.apache.http.impl.client.HttpClientBuilder; 
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.boot.SpringApplication; 
import org.springframework.boot.autoconfigure.SpringBootApplication; 
import org.springframework.context.annotation.Bean; 
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 
import org.springframework.web.client.RestTemplate; 

@SpringBootApplication 
public class App { 
    Logger log = LoggerFactory.getLogger(App.class); 

    public static void main(String[] args) { 
     System.out.println("Start!"); 
     SpringApplication.run(StateServiceApplication.class, args); 
     System.out.println("End!"); 
    } 

    @Bean 
    public RestTemplate restTemplate() { 
     log.trace("restTemplate()"); 
     HttpHost proxy = new HttpHost("proxy_url", 8080); 
     PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); 
     // Increase max total connection to 200 
     cm.setMaxTotal(200); 
     cm.setDefaultMaxPerRoute(50); 

     RequestConfig requestConfig = RequestConfig.custom().setProxy(proxy).build(); 

     HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); 
     httpClientBuilder.setDefaultRequestConfig(requestConfig); 
     httpClientBuilder.setConnectionManager(cm); 
     HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
       httpClientBuilder.build()); 
     return new RestTemplate(requestFactory); 
    } 
} 

RESTfulなインターフェースを表すクラス:

package stateservice; 

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.web.bind.annotation.RequestBody; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RequestMethod; 
import org.springframework.web.bind.annotation.RestController; 

import foo.bar.XmlData 

@RestController 
public class StateController { 

    private static Logger log = LoggerFactory.getLogger(DataController.class); 

    @Autowired 
    ForwarderService forwarder; 


    @RequestMapping(value = "/data", method = RequestMethod.POST) 
    public String postState(@RequestBody XmlData data) { 
     forwarder.forward(data); 
     return "Done!"; 
    } 
} 

最後に、フォワーダー:

package stateservice; 

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.http.HttpEntity; 
import org.springframework.http.HttpHeaders; 
import org.springframework.http.MediaType; 
import org.springframework.http.ResponseEntity; 
import org.springframework.scheduling.annotation.Async; 
import org.springframework.stereotype.Service; 
import org.springframework.web.client.RestTemplate; 

import foo.bar.Converter; 
import foo.bar.XmlData; 

@Service 
public class ForwarderService { 
    private static Logger log = LoggerFactory.getLogger(ForwarderService.class); 

    String uri = "forward_uri"; 

    @Autowired 
    RestTemplate restTemplate; 

    @Async 
    public String forward(XmlData data) { 
     log.trace("forward(...) - start"); 
     String json = Converter.convert(data); 
     HttpHeaders headers = new HttpHeaders(); 
     headers.setContentType(MediaType.APPLICATION_JSON); 

     ResponseEntity<String> response = restTemplate.postForEntity(uri, 
       new HttpEntity<String>(json, headers), String.class); 
     // responseEntity.getBody(); 
     // log.trace(responseEntity.toString()); 
     log.trace("forward(...) - end"); 
     return response.getBody(); 
    } 
} 

ただし、接続マネージャめったにありませんCLOSE_WAIT状態(netstatを使って見ることができる)の接続でシステムが浸水してしまいます。プール内のすべての接続はリースされますが、解放されません。CLOSE_WAIT状態の接続数がulimitに達すると、「開いているファイルが多すぎます」という例外が発生します。

コードのマルチスレッド性私は、ソケットが閉じられない/接続が解放されると思われます。なぜなら、他のスレッドがそれらをブロックしているからです。

私は問題を解決するために何か助けやヒントをいただければ幸いです。

+0

コードは問題ありません。サーバーが応答を提供していますか?あなたは 'requestFactory'で' setConnectTimeout'と 'setReadTimeout'プロパティを設定しようとしましたか? 'ForcededService'を(' @ Async'なしで)同期して呼び出すと動作しますか? – Ruben

+0

はい、サーバーが応答します(ステータス '200 OK')。私はタイムアウトを設定し、転送方式を同期的に呼び出しました - 何も助けられませんでした。 – pczora

+0

私が持っている唯一の提案はKeepAlive戦略を構成することです。 [http client docs](http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html)の第2.6章を参照してください。 – Ruben

答えて

2

のApache HttpEntityとのトリックがあります - ロックされた接続を解放するために - 応答は、完全にを消費し、を閉じなければなりません。詳細については、EntityUtilsHttpEntityドキュメントを参照してください。

EntityUtils.consume(response); 

バージョン4.3のApache HttpClientをバック#閉じる()メソッドがCloseableHttpResponseに呼び出された接続をプールに解放しているので。 HttpComponentsClientHttpResponse.javaでただし、この機能は唯一のバージョン4.0.0-RELEASE以降春のWebでサポートされてい

、(メソッド#閉じるを参照してください):

@Override 
public void close() { 
    // Release underlying connection back to the connection manager 
    try { 
     try { 
      // Attempt to keep connection alive by consuming its remaining content 
      EntityUtils.consume(this.httpResponse.getEntity()); 
     } finally { 
      // Paranoia 
      this.httpResponse.close(); 
     } 
    } 
    catch (IOException ignore) { 
    } 
} 

成功への鍵は//パラノイア」でマークされた行であります" - 明示的な.close()呼び出し。実際にプールに戻って接続を解放します。

関連する問題