10

używam wiosny do osiągnięcia następujących celów:PoolingHttpClientConnectionManager nie zwalnia połączenia

na serwerze, mam odbierać dane za pośrednictwem interfejsu REST w formacie XML. Chcę przekształcić dane w JSON i POST go na inny serwer. Mój kod (usunąłem kilka czułych classnames/URL, aby uniknąć gniewu mojego pracodawcy) wygląda następująco:

Główna klasa/Konfiguracja:

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); 
    } 
} 

Klasa reprezentująca relaksującego interfejs:

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!"; 
    } 
} 

Wreszcie Forwarder:

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(); 
    } 
} 

jednak rzadko Connection Manager Wydaje się, że wydaje on połączenia do ponownego użycia, a dodatkowo system jest zalewany połączeniami w stanie CLOSE_WAIT (co można zobaczyć za pomocą netstat). Wszystkie połączenia w puli zostaną wydzierżawione, ale nie zostaną zwolnione, a gdy tylko liczba połączeń w stanie CLOSE_WAIT osiągnie wartość ulimit, otrzymuję "Zbyt wiele otwartych plików" - wyjątki

Z powodu wielowątkowej natury kodu Podejrzewam, że gniazda nie mogą być zamknięte/połączenia zostaną zwolnione, ponieważ jakiś inny wątek je blokuje.

Byłbym wdzięczny za pomoc lub wskazówki, które możesz mi podać, aby rozwiązać problem.

+0

Twój kod wydaje się być w porządku. Czy na pewno serwer dostarcza odpowiedzi? czy próbowałeś ustawić właściwości 'setConnectTimeout' i' setReadTimeout' w 'requestFactory'? Czy to działa, gdy wywołujesz 'ForwardedService' synchronicznie (bez" @ Async ")? – Ruben

+0

Tak, serwer odpowiada (Stan '200 OK'). Ustawiłem limity czasu i synchronicznie nazwałem metodę przekazywania - nic nie pomogło. – pczora

+0

Jedyna sugestia, jaką mam, to skonfigurować strategię KeepAlive. Zajrzyj do rozdziału 2.6 w [dokumentach klienta http] (http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html) – Ruben

Odpowiedz

2

Jest trik z Apache HttpEntity - aby zwolnić połączenie zamknięte - odpowiedź musi być w pełni spożywane i zamknięte. Zobacz EntityUtils i HttpEntity dokumenty do szczegółów:

EntityUtils.consume(response); 

Od wersji 4.3 Apache HttpClient zwalnia połączenie z powrotem do puli, gdy metoda #Zamknij() jest wywoływana na CloseableHttpResponse.

Jednak ta funkcja jest obsługiwana przez wiosennym Web tylko od wersji 4.0.0-RELEASE, patrz metoda #Zamknij() w HttpComponentsClientHttpResponse.java:

@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) { 
    } 
} 

Kluczem do sukcesu jest linia oznaczona przez „// Paranoia "- jawne wywołanie .close(). W rzeczywistości zwalnia połączenie z powrotem do puli.

Powiązane problemy