10

sto usando primavera per raggiungere i seguenti obiettivi:PoolingHttpClientConnectionManager non rilascia le connessioni

Su un server, ricevo i dati attraverso un'interfaccia REST in un formato XML. Voglio trasformare i dati in JSON e inviarli a un altro server. Il mio codice (ho tolto alcune sensibili classnames/URL per evitare l'ira del mio datore di lavoro) si presenta così:

principale classe/Configurazione:

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

La classe che rappresenta l'interfaccia 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!"; 
    } 
} 

Infine, lo spedizioniere:

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

Tuttavia, il Connection Manager di rado sembra che rilasci le connessioni per il riutilizzo, e in aggiunta, il sistema si riempie di connessioni nello stato CLOSE_WAIT (che può essere visto usando netstat). Tutte le connessioni nel pool vengono concesse in lease, ma non rilasciate e non appena il numero di connessioni nello stato CLOSE_WAIT raggiunge il limite, vengono visualizzate le eccezioni "Troppe aperte"

A causa della natura multithread del codice , Sospetto che le prese non possano essere chiuse/le connessioni vengano rilasciate, perché qualche altro thread li sta bloccando in qualche modo.

Apprezzerei molto qualsiasi aiuto o suggerimento che tu possa darmi per risolvere il problema.

+0

Il tuo codice sembra ok. Sei sicuro che il server stia consegnando le risposte? hai provato a impostare le proprietà 'setConnectTimeout' e' setReadTimeout' in 'requestFactory'? Funziona quando chiamate il 'ForwardedService' in modo sincrono (senza' @ Async')? – Ruben

+0

Sì, il server risponde (Stato '200 OK'). Ho impostato i timeout e ho chiamato il metodo di inoltro in modo sincrono - nulla ha aiutato. – pczora

+0

L'unico suggerimento che ho è di configurare una strategia KeepAlive. Dai un'occhiata al capitolo 2.6 in [http client docs] (http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html) – Ruben

risposta

2

c'è un trucco con Apache HttpEntity - per rilasciare il collegamento bloccato - la risposta deve essere pienamente consumato e chiuso. Vedere EntityUtils e HttpEntity documentazione per i dettagli:

EntityUtils.consume(response); 

A partire dalla versione 4.3 di Apache HttpClient rilascia attacco posteriore alla piscina quando il metodo #Chiudi() viene chiamato sul CloseableHttpResponse.

Tuttavia questa funzione è supportata da Spring Web solo a partire dalla versione 4.0.0-release, vedere il metodo #Chiudi() in 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) { 
    } 
} 

La chiave del successo è la linea segnata da "// Paranoia "- Chiamata esplicita .close(). In realtà rilascia la connessione di nuovo al pool.

Problemi correlati