본문 바로가기

Spring

Spring restTemplate Connection pool 사용

728x90
반응형

ResteTemplate?

스프링3.0부터 제공하는 HTTP 요청을 수행하는 Synchronous 클라이언트.

JDK HttpURLConnection, Apache HttpComponents 등과 같은 기본 HTTP 클라이언트 라이브러리를 통한 템플릿 메소드 API.

스프링 5부터는 maintenance 모드로 WebClint 사용을 지향한다.

 

RestTemplate은 기존에 HttpClient를 추상화해서 제공한다.

HttpClient를 사용했을 때 빈번하게 발생하는 코드 중복 혹은 응답 컨텐츠 관리에 좋다.

 

기본적으로 RestTemplate은 커넥션풀을 사용하지 않는다. 

커넥션을 맺고 닫을 때 SimpleClientHttpRequestFactory(HttpUrlConnection래핑)를 사용한다. 

대량의 request가 서버에 들어오면 같은 host로의 연결에 커넥션을 맺고 끊음을 반복하면 낭비가 되기 때문에

커넥션 풀을 사용하는 것이 합리적이다. 

 


 

RestTemplate 동작방식

1. RestTemplate는 URI, HTTP메소드 등의 헤더에 담아서 요청하는데 HttpMessageConverter를 사용하여 requestEntity를 요청메세지로 변환한다. 

2. ClientHttpRequestFactory로 부터 ClientHttpRequest를 가져와서 요청을 보낸다.

3. ClientHttpRequest는 요청메세지를 만들어서 Http프로토콜을 통해서 서버와 통신한다. 

4. RestTemplate은 ResponseErrorHandler로 오류를 확인한 후 오류가 있다면 ClientHttpResponse에서 응답을 가져와서 처리한다.

5. HttpMessageConverter를 이용해서 응답메세지를 java Object로 변환한다.

6. 어플리케이션에 반환한다.


PoolingHttpClientConnectionManager

Apache Http Client Connection Management

아파치 HttpClient는 커넥션 풀을 지원한다. 

 

PoolingHttpClientConnectionManager는 클라이언트 커넥션풀을 관리한다. 

여러 스레드에서 커넥션 요청을 처리할 수 있고 라우트를 기반으로 풀된다. 이미 라우트에 대한 커넥션이 풀에 있으면

해당 풀을 꺼내서 사용한다. 

default 라우트 : 2개

default 최대 커넥션 : 20개

 

@Bean
public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
    PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
    // 허용되는 최대 커넥션 수
    poolingHttpClientConnectionManager.setMaxTotal(200);
    // setMaxPerRoute는 경로를 미리 알고 있는 경우 사용
    // setMaxPerRoute에 의해 경로가 지정되지 않은 호출에 대해 connection 갯수를 설정
    // 라우팅할 경로에 대한 커넥션
    poolingHttpClientConnectionManager.setDefaultMaxPerRoute(20);

    return poolingHttpClientConnectionManager;
}

ConnectionKeepAliveStrategy

헤더에 Keep-alive 응답이 없으면 HttpClient는 연결을 무기한 활성상태로 유지할 수 있다. 

연결이 재사용되기 전에 유휴 상태로 유지될 수 있는 기간을 설정할 수 있는 인터페이스이다.

@Bean(name = "keepAliveStrategy")
public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
   return new ConnectionKeepAliveStrategy() {
       @Override
       public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
           HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
           while(it.hasNext()) {
               HeaderElement he = it.nextElement();
               String param = he.getName();
               String value = he.getValue();
               if(value != null && param.equalsIgnoreCase("timeout")) {
                   return Long.parseLong(value) * 1000;
               }
           }
           return 20*1000;
       }
   };
}

헤더에 명시된 호스트의 keep-alive정책을 적용하려고 시도한 후 정보가 응답헤더에 없으면 20초간 연결을 유지한다. 


IdleConnectionMonitorThread

모든 연결을 주기적으로 확인하고 사용되지 않고 유휴시간이 경과한 연결을 해제하는 스레드.

@Bean(name ="idleConnectionMonitor")
public Runnable idleConnectionMonitor(final PoolingHttpClientConnectionManager connectionManager) {
    return new Runnable() {
        @Override
        @Scheduled(fixedDelay = 10000)
        public void run() {
            try {
                if (connectionManager != null) {
                    connectionManager.closeExpiredConnections();
                    connectionManager.closeIdleConnections(CLOSE_IDLE_CONNECTION_WAIT_TIME, TimeUnit.MILLISECONDS);
                } else {
                    log.trace("run IdleConnectionMonitor - Http Client Connection manager is not initialised");
                }
            } catch (Exception e) {
                log.error("run IdleConnectionMonitor - Exception occurred. msg={}, e={}", e.getMessage(), e);
            }
        }
    };
}

CloseableHttpClient

HttpClient와 Closeable인터페이스를 구현한 추상 클래스 

기본적으로 connection pool을 지원하고 위에 설정한 PoolingHttpClientConnectionManager를 사용한다.

@Bean(name = "httpClient")
public CloseableHttpClient httpClient(final PoolingHttpClientConnectionManager poolManager,
                                      final ConnectionKeepAliveStrategy strategy) {
    RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(REQUEST_TIMEOUT)
            .setConnectTimeout(CONNECT_TIMEOUT)
            .setSocketTimeout(SOCKET_TIMEOUT).build();

    return HttpClients.custom()
            .setDefaultRequestConfig(requestConfig)
            .setConnectionManager(poolManager)
            .setKeepAliveStrategy(strategy)
            .build();
}

setConnectionRequestTimeout : 서버 연결을 맺을 때 타임아웃

setConnectionManager : 커넥션 풀로부터 꺼내올 때 타임아웃

setSocketTimeout : 요청/응답 간 타임아웃 (read time out)


HttpComponentsClientHttpRequestFactory

RestTemplate의 Connection수(HttpClient) 와 timeout 등의 설정을 할 수 있도록 도와주는 Factory

@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory(final CloseableHttpClient httpClient) {
    HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
    clientHttpRequestFactory.setHttpClient(httpClient);
    return clientHttpRequestFactory;
}

 

RestTemplate

@Bean(name = "restTemplate")
public RestTemplate restTemplate(final HttpComponentsClientHttpRequestFactory factory) {
    RestTemplate restTemplate = new RestTemplate(factory);
    return restTemplate;
}

Connection Pool 없이 사용할 때 

@RequestMapping("/restTemplate")
public ResponseEntity<String> restTemplate() {
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    factory.setReadTimeout(5000); // 읽기시간초과, ms
    factory.setConnectTimeout(3000); // 연결시간초과, ms
    CloseableHttpClient httpClient = HttpClientBuilder.create().setMaxConnTotal(100)// connection pool 적용
        .setMaxConnPerRoute(5) // connection pool 적용
        .build();
    factory.setHttpClient(httpClient); // 동기실행에 사용될 HttpClient 세팅
    RestTemplate restTemplate = new RestTemplate(factory);

    ResponseEntity<String> result = restTemplate.exchange("http://localhost:8080/prod/test", HttpMethod.GET, null, String.class);
    return result;
}

 

 

Connection Pool 적용

private final RestTemplate restTemplate;

@RequestMapping("/restTemplate")
public ResponseEntity<String> restTemplate() {
    ResponseEntity<String> result = restTemplate.exchange("http://localhost:8080/prod/test", HttpMethod.GET, null, String.class);
    return result;
}

참고 레퍼런스 

https://sjh836.tistory.com/141

https://howtodoinjava.com/spring-boot2/resttemplate/resttemplate-httpclient-java-config/

728x90
반응형