programing

풀링HttpClientConnectionManager가 연결을 해제하지 않음

abcjava 2023. 7. 30. 16:45
반응형

풀링HttpClientConnectionManager가 연결을 해제하지 않음

Spring을 사용하여 다음을 달성하고 있습니다.

서버에서 나는 XML 형식의 REST 인터페이스를 통해 데이터를 수신합니다.저는 데이터를 JSON으로 변환하여 다른 서버에 게시하고 싶습니다.제 코드(고용주의 분노를 피하기 위해 중요한 클래스 이름/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!";
    }
}

마지막으로 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();
    }
}

그러나 Connection Manager가 재사용을 위해 연결을 해제하는 경우는 거의 없으며, 시스템에 CLOSE_WAIT 상태의 연결이 넘쳐납니다(netstat를 사용하여 확인할 수 있음).풀의 모든 연결이 리스되지만 릴리스되지는 않으며 CLOSE_WAIT 상태의 연결 수가 최대 제한에 도달하면 'Too many open files'-exceptions가 표시됩니다.

코드의 멀티 스레드 특성상 소켓을 닫을 수 없거나 연결을 해제할 수 없는 것이 다른 스레드가 차단하고 있기 때문인 것 같습니다.

문제 해결을 위해 도움이나 힌트를 주시면 정말 감사하겠습니다.

Apache HttpEntity에는 잠긴 연결을 해제하는 요령이 있습니다. 응답을 완전히 사용하고 닫아야 합니다.자세한 내용은 EntityUtilsHttpEntity 문서를 참조하십시오.

EntityUtils.consume(response);

버전 4.3 Apache HttpClient는 CloseableHttpResponse에서 #close() 메서드를 호출하면 풀에 대한 연결을 다시 해제합니다.

그러나 이 기능은 버전 4.0.0-REASE 이후에만 Spring Web에서 지원됩니다. HttpComponentClientHttpResponse의 메서드 #close()를 참조하십시오.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) {
    }
}

성공의 열쇠는 "//Paranoia" - 명시적 .close() 호출로 표시된 라인입니다.실제로 풀에 대한 연결을 해제합니다.

언급URL : https://stackoverflow.com/questions/32909653/poolinghttpclientconnectionmanager-does-not-release-connections

반응형