10

Я использую Spring для достижения следующего:PoolingHttpClientConnectionManager не освобождает соединения

На сервере, я получаю данные через интерфейс REST в качестве XML-формате. Я хочу преобразовать данные в JSON и POST на другой сервер. Мой код (я удалил некоторые чувствительные имена классов/URL, чтобы избежать гнева моего работодателя) выглядит следующим образом:

Основной класс/Конфигурация:

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

Ваш код выглядит нормально. Вы уверены, что сервер доставляет ответы? вы пытались установить свойства 'setConnectTimeout' и' setReadTimeout' в 'requestFactory'? Работает ли он, когда вы вызываете 'ForwardedService' синхронно (без' @ Async')? – Ruben

+0

Да, сервер отвечает (статус «200 OK»). Я установил таймауты и синхронно вызвал метод пересылки - ничего не помогло. – pczora

+0

Единственное, что у меня есть, это настроить стратегию KeepAlive. Взгляните на главу 2.6 в [http client docs] (http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html) – Ruben

ответ

2

Существует трюк с Apache HttpEntity - освободить заблокированное соединение - ответ должен быть ПОЛНОСТЬЮ потребляется и закрыты. См EntityUtils и HttpEntity документы для деталей:

EntityUtils.consume(response); 

Начиная с версии 4.3 Apache HttpClient высвобождает соединение обратно в пул, когда метод # Закрыть() вызывается на CloseableHttpResponse.

Однако эта функция поддерживается Spring Web только начиная с версии 4.0.0-RELEASE см метод() # Закрыть в 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) { 
    } 
} 

Ключ к успеху является линия отмечена «// Паранойя "- явный вызов .close(). Фактически он отправляет соединение обратно в пул.

Смежные вопросы