오늘 인텔리제이로 작업하다가
Caused by: java.net.SocketTimeoutException: connect timed out
라는 에러가 떠서, 관련 CS 지식 타임아웃에 대해 정리해보려고 한다.
스프링부트 API 제작 후 빌드해서 테스트하려고 하는데 직전까지만 해도 잘 돌아가던 빌드가 왜 안되었을까?
.
.
인줄 알았으나 그냥 경고 표시였다.
이번 기회에 타임아웃에 대해 제대로 알고 가보자.
서버 간 통신이 많아지는 MSA 구조가 주목받으면서 내부 시스템 안에서도 서로 클라이언트와 서버가 되어 데이터를 주고 받는 비중이 점점 커지고 있다. 이런 상황에서 통신을 요청하는 클라이언트는 다양한 Timeout 오류를 만날 수 있는데, 이런 Timeout에 대한 종류를 잘 구별한다면 각각의 상황에 따라 구분해서 처리할 수 있다.
타임아웃
- 프로그램이 특정한 시간 내 성공적으로 수행되지 않아 진행이 자동적으로 중단되는 것
- 응답을 무한정 기다릴 수 없기 때문에 기다릴 시간을 정해야 함
타임아웃 사례
- Socket(양방향 통신), Http(단방향 통신)에서 다양하게 활용
- JDBC
- JDBC Driver Type4는 소켓을 사용하여 DBMS에 연결하는 방식
- Connection Timeout : DB 커넥션 요청을 했으나, 특정 시간 내 연결이 안될 때
- 채팅 프로그램
- Socket Timeout : 채팅 프로그램에서 서버로부터 특정 시간 응답이 없을 때
- WEB
- Connection Timeout : 클라이언트에서 서버로 request를 날렸을 때 연결되지 않은 상태로 특정시간 이상 대기
관련 패키지
- java.net
- HttpClient
1) java.net 패키지
- 네트워킹 응용 프로그램을 구현하기 위한 클래스를 제공하는 패키지
- java.net 패키지는 크게 두 섹션으로 나뉨
- 저수준 API
- Addresses : 네트워크 식별자 ex) IP 주소
- Sockets : 기본 양방향 데이터 통신 메카니즘
- Interfaces : 네트워크 인터페이스
- 고수준 API
- URIs : Universal Resource Identifiers
- URLs : Universal Resource Locators
- Connections : URL이 가리키는 리소스에 대한 연결
- 저수준 API
java.net 패키지에서 제공하는 4개의 socket
- Socket : TCP 클라이언트 API, 일반적으로 원격 호스트에 연결하는 데 사용됨
- ServerSocket : TCP 서버 API, 일반적으로 클라이언트 소켓의 연결 허용
- DatagramSocket : UDP 엔드포인트 API, datagram 패킷을 주고 받는데 사용
- MulticastSocket : multicast 그룹을 처리하는 DatagramSocket의 하위클래스
- TCP 소켓을 통한 송수신 -> Socket.getInputStream() 메소드 및 Socket.getOutputStream()메소드를 통해 얻을 수 있는 InputStreams 및 OutputStreams를 통해 수행됨
* Socket 관련 Timeout
- Connection Timeout
- Socket Timeout / Read Timeout
1. Connection Timeout
- 클라이언트가 서버측으로 Connection을 맺길 원하지만 서버의 장애 상황으로 맺어지지 못할 때 발생하는 timeout
- 이 경우에는 서버에 접근이 안되는 경우라서 클라이언트는 서버의 장애 상황으로 간주할 수 있음. 보통 이 경우에 클라이언트는 일시적인 오류 상황으로 구분하여 처리를 하거나 미리 정의된 dafault 데이터나 cache 데이터로 fallback 처리하기도 함.
- Connection 과정
- TCP 소켓 통신에서 클라이언트와 서버가 연결될 때 정확한 전송을 보장하기 위해 상대방 컴퓨터와 사전에 세션을 수립함. (3-way handshake)
- 3-way handshake가 정상적으로 끝나야 Connection이 됐다고 표현할 수 있음. 즉, Connection Timeout이란 3-way HandShake가 정상적으로 수행되어 서버에 연결되기까지 소요된 시간임.
- TCP 3-way HandShake 절차
- 1) 클라이언트 A 는 B 서버에 접속을 요청하는 SYN 패킷을 보냄. 이때 A는 SYN을 보내고 SYN/ACK 응답을 기다리는 SYN_SENT 상태가 됨
- 2) B 서버는 SYN 요청을 받고 A에게 요청을 수락한다는 ACK와 SYN flag가 설정된 패킷을 발송하고 A가 다시 ACK으로 응답하길 기다린다. 이때 B 서버는 SYN_RECEIVED 상태가 됨
- 3) A는 B에게 ACK을 보내고 이후부터는 연결이 이뤄지고 데이터가 오가게 되는 것이다. 이때 B 서버 상태는 ESTABILSHED이다.
- SYN : synchronize sequence numbers
- ACK : acknowledgement
- 4-way handshake : 3-way handshake는 TCP의 연결을 초기화할 때 사용한다면, 4-way handshake는 세션을 종료하기 위해 수행되는 절차임
2. Socket Timeout
- 클라이언트와 서버가 connection을 맺은 후 서버는 데이터를 클라이언트에게 전송하게 됨. 이때 실제 데이터를 주고 받는 과정은 하나의 데이터 덩어리가 아닌 여러개의 패킷으로 나눠서 전송되는데 각 패킷이 전송될 때 시간 차(Gap)가 있음. 이 차이 시간의 제한(임계치)을 SocketTimeout이라고 함.
- read timeout과의 관계
- 클라이언트와 서버가 connection은 맺어졌지만 I/O (Input/Out) 작업이 길어지거나 락이 걸려 요청이 처리되지 못하고 있을 때 클라이언트는 더 이상 기다리지 못하고 커넥션을 끊음. 이런 상황을 Read Timeout이라고 함.
- java.net에서는 socket timeout과 read timeout을 혼용하며, setSoTimeout() 메소드를 사용함
2-1. Read Timeout
- 클라이언트와 서버가 connection에는 성공했지만 실제 데이터를 전송하는 I/O 과정이 길어지는 경우 일정 시간이 경과되면 클라이언트는 connection을 끊게 됨.
- 보통 주고 받는 데이터의 양이나 네트워크 속도에 따라서 대응을 다르게 함. 만약 데이터의 양이 크다면 이를 분할해서 받을 수 있도록 API 자체 Spec을 변경하거나 Retry 전략을 사용할 수 있고, 속도가 느려서 발생하는 상황이라면 전반적으로 네트워크 대역폭 증가를 위한 인프라 작업을 고려할 수 있음
* Connection, Socket/Read Timeout과 관련된 예외
- java.net.Socket.Exception : Thrown to indicate that there is an error creating or accessing a Socket. -> connection timeout
- java.net.SocketTimeoutException : Signals that a timeout has occurred on a socket read or accept. -> socket timeout, read timeout
2) HttpClient 라이브러리
Apache HttpClient
- HTTP 프로토콜을 손쉽게 사용할 수 있게 해주는 클라이언트측 HTTP 전송 라이브러리
- Apache HttpComponents 제품군의 HttpClient는 http 통신을 위한 표준이 되어옴
- httpURLConnection의 단점(connection pooling)을 채우는 다양한 API를 가진 성숙한 프로젝트
- Apache HttpClient를 이용하면 간편하게 HTTP request를 보낼 수 있음. 간혹 웹 서버를 만들면서 다른 서버로부터 request를 보내 response받아 데이터를 처리해야할 때가 있음. 이때 HttpClient를 이용하면 간단하게 구현 가능
- java.net 패키지와의 차이점
- java.net 패키지는 HTTP를 통해 리소스에 액세스하기 위한 기본 기능을 제공하지만, 많은 애플리케이션에 필요한 완전한 유연성이나 기능을 제공하지 않음
- HttpClient 패키지는 최신 HTTP 표준 및 권장 사항의 클라이언트 측을 구현하는 효율적이고 최신이며 기능이 풍부한 패키지를 제공해 이 공백을 채우려고 함
- 확장을 위해 설계된 HttpClient는 기본 HTTP 프로토콜에 대한 강력한 지원을 제공하는 동시에 웹 브라우저, 웹 서비스 클라이언트 또는 분산 통신을 위해 HTTP 프로토콜을 활용하거나 확장하는 시스템과 같은 HTTP 인식 클라이언트 응용 프로그램을 구축하는 모든 사람에게 유용할 수 있음
HttpClient에서 제공하는 timeout 관련 메소드
- setConnectTimeout : 서버와 연결을 맺을 때의 타임아웃
- setConnectionRequestTimeout : ConnectionManager(커넥션풀)로부터 꺼내올 때의 타임아웃
- setSocketTimeout : 요청/응답간의 타임아웃
- Connection Pooling
- HttpClient로 빈번히 connection을 맺었다가 사용이 끝나고 끊고 하다 보면 더 이상 Connection을 열 수 없는 경우가 발생할 수 있음
- connection을 닫는다고 호출을 해도 실제로는 어느정도 TIME_WAIT 상태에 있다가 끊어지는데 이런 것들이 많이 쌓여 있으면 File Descriptor가 꽉 찼다는 에러(Too Many Open Files)가 나면서 connection을 맺지 못하게 된다.
- 이런 현상을 방지하기 위해서 Connection을 재사용할 수 있도록 HttpClient에서 제공하는 Connection Pool을 사용함 (getHttpClient를 호출할 때 Connection Pool이 지정된 사이즈로 생성되고 Connection을 하나 만들어 리턴함)
- Pool을 사용할 때마다 항상 주의해야할 것은 반환을 꼭 해 줘야한다!
Timeout 예시
# HttpClient 4.3 (Configure Timeouts Using the New 4.3 Builde)
int timeout = 5;
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout * 1000)
.setConnectionRequestTimeout(timeout * 1000)
.setSocketTimeout(timeout * 1000).build();
CloseableHttpClient client =
HttpClientBuilder.create().setDefaultRequestConfig(config).build();