티스토리 뷰

Spring 에서 제공하는 http client로는 대표적으로 RestTemplate이 있다. 이 RestTemplate으로 http 요청을 날리게되면 기본적으로 그때마다 connection을 맺고 응답을 받으면 끊게된다. 이를 db connection pool 처럼 connection pool을 만들어서 관리할 수 있다.

 

이를 설정하기위해서는 일단 RestTemplate에 대해서 먼저 이해를 해야하는데 Spring 에서 제공하는 RestTemplate은 직접 http 요청을 하는 역할을 수행하지않는다. 직접 수행하는 클래스를 한번 래핑한 어댑터 역할을 하는 클래스이다. 기본적으로는 jdk에서 제공하는 HttpUrlConnection 클래스를 이용한다. 우리는 apache 에서 제공하는 HttpClient 클래스를 이용하려한다.

 

/**                                                                                                       
 * Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}. 
 * @param requestFactory the HTTP request factory to use                                                  
 * @see org.springframework.http.client.SimpleClientHttpRequestFactory                                    
 * @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory                            
 */                                                                                                       
public RestTemplate(ClientHttpRequestFactory requestFactory) {                                            
	this();                                                                                               
	setRequestFactory(requestFactory);                                                                    
}                                                                                                         

 

RestTemplate을 보면 디폴트 생성자 외에 ClientHttpRequestFactory 인터페이스를 받는 생성자가있다. javadoc에 나와있는대로 SimpleClientHttprequestFactory와 HttpComponentsClientHttpRequestFactory가 대표적인 구현체이다.

 

클래스명만봐도 알겠지만 따로 인자를 보내주지않으면 기본적으로 SimpleClientHttpRequestFactory를 사용하게된다. 그리고 이게 위에서 말한 jdk의 HttpUrlConnection을 이용하는것이다.

 

우리는 HttpComponentsClientHttpRequestFactory를 보내줘야한다. 이 객체를 생성하자.

 

val factory = HttpComponentsClientHttpRequestFactory(httpClient)
factory.setConnectTimeout(300)                                  
factory.setReadTimeout(300)                                     
                                                                
restTemplate = RestTemplate(factory)                            

(코틀린 코드인점을 감안해서 보자. 생성자를 이용해 객체를 생성하는 구문이다.)

 

HttpComponentsClientHttpRequestFactory 를 이용해서 RestTemplate의 timeout 설정들도 해줄 수 있다. 그런데 우리가 원하는 connection pool 설정은 아직 없다. 잘보면 생성자를 호출할때 httpClient 라는 변수를 인자로 보내는걸 알 수 있는데 이녀석이 핵심이다.

 

/**                                                                           
 * Create a new instance of the {@code HttpComponentsClientHttpRequestFactory}
 * with a default {@link HttpClient} based on system properties.              
 */                                                                           
public HttpComponentsClientHttpRequestFactory() {                             
	this.httpClient = HttpClients.createSystem();                             
}                                                                             
                                                                              
/**                                                                           
 * Create a new instance of the {@code HttpComponentsClientHttpRequestFactory}
 * with the given {@link HttpClient} instance.                                
 * @param httpClient the HttpClient instance to use for this request factory  
 */                                                                           
public HttpComponentsClientHttpRequestFactory(HttpClient httpClient) {        
	this.httpClient = httpClient;                                             
}                                                                             

 

생성자를 따라가보면 HttpClient를 매개변수로 받는걸 알 수 있다. 그리고 위에 디폴트 생성자를 보면 createSystem() 이라는 팩토리 메서드를 이용하는걸 볼 수 있는데 이경우 기본 system properties를 이용해서 HttpClient 객체를 생성하게된다.

 

우리는 이 HttpClient 객체를 만들어야하는것이다. HttpClient 객체를 만들기위한 유틸클래스로 HttpClients 가 있다.

 

/**
 * Factory methods for {@link CloseableHttpClient} instances.
 * @since 4.3
 */
public class HttpClients {

    private HttpClients() {
        super();
    }

    /**
     * Creates builder object for construction of custom
     * {@link CloseableHttpClient} instances.
     */
    public static HttpClientBuilder custom() {
        return HttpClientBuilder.create();
    }

    /**
     * Creates {@link CloseableHttpClient} instance with default
     * configuration.
     */
    public static CloseableHttpClient createDefault() {
        return HttpClientBuilder.create().build();
    }

    /**
     * Creates {@link CloseableHttpClient} instance with default
     * configuration based on system properties.
     */
    public static CloseableHttpClient createSystem() {
        return HttpClientBuilder.create().useSystemProperties().build();
    }

    /**
     * Creates {@link CloseableHttpClient} instance that implements
     * the most basic HTTP protocol support.
     */
    public static CloseableHttpClient createMinimal() {
        return new MinimalHttpClient(new PoolingHttpClientConnectionManager());
    }

    /**
     * Creates {@link CloseableHttpClient} instance that implements
     * the most basic HTTP protocol support.
     */
    public static CloseableHttpClient createMinimal(final HttpClientConnectionManager connManager) {
        return new MinimalHttpClient(connManager);
    }

}

 

몇가지 팩토리 메서드들을 제공하고있는데 우리는 custom 설정을해서 사용할것이므로 custom() 메서드를 호출해서 Builder 객체를 이용하면된다.

 

val httpClient: HttpClient = HttpClients.custom()                 
        .setMaxConnTotal(120)                                     
        .setMaxConnPerRoute(60)                                   
        .build()                                                  
                                                                  
val factory = HttpComponentsClientHttpRequestFactory(httpClient)  
factory.setConnectTimeout(300)                                    
factory.setReadTimeout(300)                                       
                                                                  
restTemplate = RestTemplate(factory)                              

 

이런 형태로 생성해주면된다. maxConnTotal은 연결을 유지할 최대 숫자이고 maxConnPerRoute는 특정 경로당 최대 숫자이다. 이 외에도 각종 설정들을 제어할 수 있으니 공식 홈페이지를 참고해서 설정을 해주면된다.

 

#2019.05.25 내용 추가

포스팅을 작성하고 문득 생각이 들었다. 커넥션 풀이라 함은 미리 연결을 유지하고있다는 의미인데 http 에서 연결을 유지하고있으려면 서버에서 keep-alive 를 지원하고있어야하는거 아닌가? 라는 의문이 든것이다. 마침 나랑 같은 고민을 하신분이 댓글도 달아주셔서 공식문서를 뒤져봤는데 keep-alive 관련 언급은 전혀 없었다.(내가 못찾은건가.. 꽤 많이 뒤져봤는데......)

그래서 직접 테스트를 진행해보기로했다. 테스트하기위해선 rest template을 사용하는 클라이언트 외에 서버 애플리케이션도 필요하다. 난 테스트를 위해 단순히 header에 keep-alive를 설정하기만하는 샘플 애플리케이션을 서버로 이용했다.

 

val httpClient = HttpClients.custom()
                .setMaxConnTotal(3)
                .setMaxConnPerRoute(3)
                .build()

 

테스트용 설정은 커넥션 풀에 커넥션을 3개 생성해놓도록 설정하고, 100개의 스레드로 rest template을 호출하도록했다.

 

val tp = Executors.newFixedThreadPool(20)

val cd = CountDownLatch(20)

for (i in 0..100) {
  tp.execute {
    apiRepository.call(param, String::class.java)
    cd.countDown()
  }
}

cd.await()
tp.shutdown()

대략 이런식으로 말이다. (참고로 저 apiRepository는 https://multifrontgarden.tistory.com/251 여기서 다룬 repository를 사용한다.)

 

서버에서 keep-alive 미지원

wire shark 를 이용해 패킷을 보는화면이다. 자세하게 올리지도않았고, 자세히 볼필요도 없긴한데 매 호출시 핸드쉐이크가 발생하는 내용이다.(프로토콜에 TCP로 나온부분이 핸드쉐이크 부분이고, HTTP로 표현되는 부분이 실제로 HTTP request/response 부분이다.) 그리고 커넥션 풀을 3개를 설정했지만 실제 사용한 클라이언트 소켓도 3개를 훨씬 넘겨서 사용했다.

 

Caused by: java.net.SocketTimeoutException: connect timed out

 

덤으로 이런 커넥션 타임아웃도 몇개 발생했다.

 

서버에서 keep-alive 지원

이번엔 keep-alive를 지원하는 서버를 호출했을때다. 캡쳐된화면을보면 위에꺼랑 확연하게 다른걸 볼 수 있다. 처음과 끝에서만 핸드쉐이크가 발생하고 keep-alive를 유지하고있기때문에 HTTP 호출로 가득하다.(녹색)

그리고 실제로 이 연결에 사용한 소켓 포트도 내가 설정한 3개만 사용한다. (3개의 소켓 포트로 100번을 다 호출하고있다.)

 

물론 keep-alive 미지원에서 발생했던 커넥션 타임아웃같은건 발생하지않았다. 이 테스트로 인해 클라이언트쪽에서 아무리 커넥션 풀을 만든다해도 서버에서 keep-alive를 지원하지않으면 말짱도로묵이라는걸 알 수있다. 하지만 현재 가장 많이 사용되는 http 1.1의 경우 기본 설정이 keep-alive 지원이며, MSA 형태로 내부 애플리케이션끼리 호출하는 경우엔 서버쪽 애플리케이션도 우리의 영역 안에 있으므로 충분히 효과를 볼 수 있을것이다.

'Java > spring' 카테고리의 다른 글

spring bean lite mode  (0) 2019.08.03
spring boot 2에서 junit 5 사용하기  (6) 2019.05.16
Netflix Hystrix  (4) 2018.12.23
Spring Cloud Config 2  (1) 2018.12.08
Spring Cloud Config 1  (0) 2018.12.07
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함