RestTemplate 사용시 주의사항

최근에 RestTemplate을 사용하면서 겪은 장애상황과 해결포인트에 대해 간략히 정리해 보았다.

발단

jetty를 이용한 웹서비스에서 다른 백엔드 컴포넌트의 rest api를 호출하는 부분의 구현방식을

변경해야 할 요구사항이 생겼다.

총 api 호출횟수는 (해당 사용자의 총 데이터 수 / 100)로 데이터가 많은 유저일수록

api호출횟수가 늘어났고, 전보다 rest api의 응답속도가 빨라져 단위시간당 api 호출횟수가

더 많아진 상황이었다.

이 부분에 RestTemplate을 적용해 배포하고, 대략 20분 뒤 웹서비스의 응답이 급격히

느려지면서 access log의 요청횟수가 평소의 절반으로 떨어지기 시작했다.

그리고 VisualVM으로 해당서버를 모니터링 해보니 jetty의 worker thread가 평소와 다르게

계단 모양으로 증가하는 추세를 보였다.

문제원인 분석

일단 의심스러운 구간별로 처리시간을 로그로 남겨 확인해 본 결과, 예상대로 RestTemplate으로

rest api를 호출하는 부분이 오래 걸리는 것을 데이터로 확인했다.

그리고 해당서버의 시스템리소스를 확인해보니, 유독 tcp connection 갯수가 평소의 적게는 3배

많게는 8배까지 증가한 걸 확인할 수 있었다.

그렇다면 왜 tcp connection이 갑자기 급격하게 증가했을까?

해결포인트

앞서 언급했듯이 호출횟수도 많긴 하지만 그보다 RestTemplate은 기본적으로 connection

pooling을 하지 않기 때문에, 매 요청마다 새로운 로컬포트를 열어서 서버로의 커넥션을

생성하게 된다. 이로 인해 tcp connection이 급격하게 증가하게 되었고,

이를 해결하기 위해 아래와 같이 connection pooling관련 설정을 추가한 HttpClient객체를

HttpComponentsClientHttpRequestFactory에 주입해 해결했다.

HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setConnectTimeout(xxx);
httpRequestFactory.setReadTimeout(xxx);
<strong>HttpClient httpClient = HttpClientBuilder.create()
 .setMaxConnTotal(xxx)
 .setMaxConnPerRoute(xxx)
 .build();
httpRequestFactory.setHttpClient(httpClient);</strong>
RestTemplate restTemplate = new RestTemplate(httpRequestFactory);

여기서 중요한 건,  HttpClient의 maxConnTotal과 maxConnPerRoute인데 이에 대한

설명은 https://stackoverflow.com/a/31892901 에서 확인하면 된다.

maxConnTotal과 maxConnPerRoute 갯수는 각 서비스의 상황에 맞게 적절한 값을

넣어주면 된다.

내가 겪은 상황이 일반적인 상황은 아니지만, 혹시나 비슷한 상황하에 RestTemplate을

사용한다면 꼭 connection pooling 설정추가를 고려해 보았으면 한다.

참고로, 아래링크는 최대 커넥션 수에 영향을 주는 클라이언트/서버관련 사항들이다.

https://stackoverflow.com/a/3923785

Advertisements

[Spring]@Async 어노테이션 사용하여 비동기로 메서드 실행하기

저번에 회사에서 애플의 apns를 이용해 각각 애플 디바이스로 푸시를 할 수 있는

기능을 만들었습니다.

알람기능은 굳이 “동기“방식으로 구현할 필요가 없었기 때문에 “비동기“방식으로

구현했고, 그때 사용한것이 @Async 어노테이션이었습니다.

@Async에 대한 레퍼런스 문서는 링크를 참조하시면 됩니다.

사용방법은 무척이나 간단합니다.

1. 먼저 비동기로 실행할 놈을 빈으로 설정합니다.

<task:executor id="testExecutor" pool-size="10"/>

<task:annotation-driven executor="testExecutor"/>

pool-size는 각각 상황에 맞게 적절하게 지정해주시면 됩니다.

2. @Async 어노테이션을 비동기로 실행할 메서드에 붙여줍니다.

이때 주의할 점은 @Async 어노테이션이 붙은 메서드의 리턴타입은 void 혹은

    java.util.concurrent.Future타입이라는 것입니다.

다른 타입으로 지정시 무조건 null이 넘어오므로 주의해야 합니다.

@Async
public Future<Integer> test() {
    System.out.println("===========================");
    System.out.println("test메서드 실행###");
    System.out.println("===========================");

    return new AsyncResult<Integer>(-2);
}

마지막으로 간단한 테스트케이스를 작성해 보았습니다.

@Test
public void async_리턴값확인() throws InterruptedException, ExecutionException {
	assertThat(asyncTest.test().get(), is(-2));
}

해당 테스트를 실행하면, 에러없이 결과가 출력됨을 확인할 수 있습니다.

*참고 url