-
HttpClient 연결 유지 관리삽질 2022. 4. 2. 06:38반응형
Connection icons created by Freepik https://www.flaticon.com/free-icons/connection Spring Application에서 HTTP 요청을 할 때 Framework에 내장된 RestTemplate를 사용하는 경우가 많다.
더보기사실 RestTemplate는 Spring 5버전 부터는 WebFlux의 WebClient가 등장하면서 DERECATED 될 예정이다.
아래 노트를 보면 새로운 기능 추가는 없을 것이고 버그 수정이나 있을 듯 하다.
NOTE: As of 5.0, the non-blocking, reactive org.springframework.web.reactive.client.WebClient offers a modern alternative to the RestTemplate with efficient support for both sync and async, as well as streaming scenarios. The RestTemplate will be deprecated in a future version and will not have major new features added going forward. WebClient는 Blocking, sync 방식 뿐만 아니라 Non-blocking, async를 지원한다.
물론 RestTemplate가 DERECATED되어도 레거시 코드에서는 사용하는데에 지장이 없으니 앞으로도 많이 보게 될 것 같다.
그래도 지금 다니는 회사에서 솔루션 개발에는 Reactive를 기반으로 하고 있다.
이 때 RestTemplate는 Connection pool을 따로 관리하지 않기 때문에 HttpClient를 커스텀하게 구성하여 사용한다.
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setReadTimeout(30000); factory.setConnectTimeout(10000); //Connection pool set HttpClient httpClient = HttpClientBuilder.create() .setMaxConnTotal(100) .setMaxConnPerRoute(10) .build(); factory.setHttpClient(httpClient); RestTemplate restTemplate =new RestTemplate(factory);
간단하게 하면 위와 같이 구성한다.
그런데 실제 프로젝트에서 REST API 구현 후 테스트 단계에서 간간히 I/O Error가 발생했다.
에러가 계속 발생하는 것이 아니라 잘 되다가 몇초간 발생해서 디버깅하기 힘들었다. (당시 입사 1개월 후 첫 프로젝트 투입이었다...ㅎㅎㅎ)
에러 로그는 아래와 같이 출력됐다.
I/O error on POST request for "https://Open API 주소": Connection reset; nested exception is javax.net.ssl.SSLException: Connection reset org.springframework.web.client.HttpClientErrorException: 404 at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91) at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:700) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:653) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613) at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:531) Suppressed: java.net.SocketException: Broken pipe (Write failed) at java.net.SocketOutputStream.socketWrite0(Native Method) at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111) at java.net.SocketOutputStream.write(SocketOutputStream.java:155) at sun.security.ssl.SSLSocketOutputRecord.encodeAlert(SSLSocketOutputRecord.java:81) at sun.security.ssl.TransportContext.fatal(TransportContext.java:355) java.net.SocketException: Connection reset at java.net.SocketInputStream.read(SocketInputStream.java:210) at java.net.SocketInputStream.read(SocketInputStream.java:141) at sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:457) at sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(SSLSocketInputRecord.java:68) at sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1095) at sun.security.ssl.SSLSocketImpl.access$200(SSLSocketImpl.java:72) at sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:815)
가장 상단에 SSLException 이라고 나오는데 해당 예외는 다양한 케이스가 존재했기 때문에(tls, ssl 버전, 메모리 크기.. 등등) 원인을 특정하긴 어려웠다.
그런데 아래쪽 로그에 socket write, read 등 I/O 자체에 문제가 발생했다는 것과, 잘 작동하던 서비스가 특정 시간이 지날 때 마다 발생한다는 걸로 유추했을 때 연결 자체에 문제가 있다고 짐작했다.
Http 로 통신할 때 소켓이 얼마나 연결을 유지할 것인지의 정보는 전달하지 않기 때문에, 보통 Server side(여기서는 Response를 하는 쪽이다)에서 Keep-Alive 헤더로 리턴하고 그 값을 사용한다.
하지만 그렇지 않은 경우에는 기존 연결이 닫혔을 때 한쪽만 소켓이 살아있는 상황이 발생한다.
당시 Server side의 자원 관리가 어떻게 되어있는지 까지는 몰랐지만, 대부분의 서버는 일정시간 이상 사용하지 않는 연결을 제거하기 때문에, 요청하는 쪽 코드에서 유휴 연결을 제거해주는 옵션을 추가해줘서 해결했다.
//Connection pool set
HttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(100)
.setMaxConnPerRoute(10)
.evictIdleConnections(60L, TimeUnit.SECONDS) // Connection error 방지.evictExpiredConnections()
.build();evictIdleConnections() 메소드를 사용하여 해당 시간마다 유휴 연결을 끊게 만들었다.
로그 레벨을 조정하여 확인해보면 별도의 스레드가 해당 유휴 상태의 연결을 제거해주는 것을 볼 수 있다.
Http 연결은 기본중에서도 바닥인데 많이 공부해야 할 것 같다.
반응형'삽질' 카테고리의 다른 글
Eclipse 소스 변경 반영 안될 때 (0) 2022.04.01 Build path specifies execution environment JavaSE-1.8. There are no JREs installed in the workspace that are strictly compatible with this environment. (0) 2020.12.03 The project cannot be built until build path errors are resolved (0) 2020.11.24 connect ECONNREFUSED ~ at TCPConnectWrap.afterConnect (0) 2020.11.06 Error: spawn cmd.exe ENOENT (0) 2020.11.06