상황
필자는 RabbitMq에서 queue의 데이터를 consume 한뒤
consume한 데이터(600/s)를 http로 전송하는 로직을 실행중이였음.
위의 로직은 여러개의 멀티스레드로 실행됨.
그러나 1분 주기로 java.net.NoRouteToHostException: 호스트로 갈 루트가 없음 이라는 예외가 나타나기 시작하였음.
방화벽을 꺼보자
# systemctl status firewalld
방화벽을 stop하고, 재부팅해도 활성화 되지 않도록 설정함.
# systemctl stop firewalld
# systemctl mask firewalld
그러나 문제는 해결되지 않았음
.https://knight76.tistory.com/entry/30021760153 참고
1NoRouteToHostException은 소켓 연결시 EADDRNOTAVAIL이 발생하면 Exception을 던지도록 되어 있다. EADDRNOTAVAIL의 의미가 Address not available 라는 의미더라구요. DNS 이슈 문제가 아니면, 순간적으로 app server에서 특정 서버 소켓 연결을 위해 클라이언트 포트를 많이 생성(임시포트(ip_local_port_range값에서 max-min의 값)까지 생성)하면서 나는 이슈로 볼 수 있을 것 같다.
포트의 범위를 늘려주어 거의 해결에 근접하였었음.
그러나 왜? 라는 의문에 대해선 해결되지 않았었음
https://cyuu.tistory.com/139 참고
https://meetup.toast.com/posts/55 참고

WireShark를 이용하여 확인하면
3way handshake를 통하여 연결된 서버와 클라이언트가 연결을 성립하여 데이터를 수신한뒤
클라이언트 62번은 더이상의 요청이 없으므로 FIN flag로 된 TCP 세그먼트를 전송
서버는 FIN에 대한 응답과 자신도 종료하기 위한 FIN flag로 된 세크먼트를 전송하여
최종적으로 클라이언트가 FIN에 대한 ACK를 전송하면서 서로 서버와 클라이언트간 연결이 종료된다
여기까지 확인했을때는 소켓을 잘 재사용 하고 있구나 라고 판단하였고
lsof -p 아이디 | wc - l
을 통하여 소켓의 개수를 실시간으로 확인했을때도 소켓의 개수는 고정적으로 유지되고 있었다.
그러나 포트가 문제 였던것이다.
필자느 서버로 http1.1 통신을 해야하는데 서버 입장에서는 정해진 포트로 통신하면 되지만
필자는 클라이언트 였기 때문에 랜덤포트로 서버에 접근하는 관점에서
초당 600의 서버와의 통신을 했었어야 했기에 여러 포트들이 생성되기 시작했으며, 리눅스 환경에서 제한된 포트의 개수와 아직 TIME_WAIT으로 인하여 반환되지 않는 포트의 수가 겹처 결국 통신을 할수 없었던 상황이 발생하였다.
netstat -atunp | grep TIME
으로 어마 무시한 TIME_WAIT과 함깨 점유중인 포트들을 확인 할 수 있었다.

HTTP 1.1에서는 스펙상 누가 먼저 소켓을 끊을지 정의하고 있지 않습니다.
하지만 일반적인 구현에서 keepalive 설정을 사용하지 않는다면, 서버에서 클라이언트로 데이터를 전송 후 즉시 close() 시스템 콜을 불러 연결을 끊습니다.
그래서 위 경우처럼 서버에 TIME_WAIT 상태의 소켓이 남게 되는 것 입니다.

https://madplay.github.io/post/ftp-active-passive
https://travelc.tistory.com/84 참고
Active Mode
[Server]
-Control Port : 21
-Data Port : 20
[Client]
-Connection Port : 5001
-Data Port : 5000
1). Client(5001) → Server(21) : 사용자인증 및 명령입력
2). Client(5000) → Server(20) : Server측에 데이터 전송 포트를 알림
3). Server(20) → Client(5000) : 실직적인 데이터 전송
장점) 서버에서 클라이언트로 접속하는 구조로 보안위험↓
서버측 ACL 관리 용이(20/21)
단점) FTP 구조를 잘 모르는 클라이언트의 경우, 정상접속 불가(방화벽, ACL..등)
※ 위 단점은 Passive Mode로 변경 할 경우 해결 가능. ※
Active Mode의 경우는 클라이언트 측의 방화벽에 20번 포트가 차단되어 있다면, 데이터 채널 연결이 불가능해집니다. 연결은 되더라도 데이터 조회부터 실패되는 경우가 발생할 수 있습니다. 따라서 서버측은 20번 포트는 아웃바운드(OUTBOUND, 내 서버에서 외부서버로 보내는 요청) 허용, 클라이언트는 인바운드(INBOUND, 외부에서 내 서버로 들어오는 요청) 허용이 방화벽 설정에 필요합니다.
Passive Mode
[Server]
-Control Port : 21
-Data Port : xxxx(1024~65535)
[Client]
-Connection Port : 5001
-Data Port : 5000
1). Client(5001) → Server(21) : 사용자인증 및 명령입력
2). Server(xxxx) → Client(20) : Client측에 데이터 전송 포트를 알림
3). Client (5000)→ Server(xxxx) : 실직적인 데이터 전송
장점) 클라이언트에서 서버로 접속하는 구조로 사용 용이
단점) Passive Mode에 사용되는 Port에 대한 ACL관리 필요(Range)
클라이언트에서 서버로 접속하는 구조로 보안위험↑
Passive Mode의 경우는 서버 측의 데이터 채널 포트가 막혀있는 경우 데이터 채널 연결이 불가능하게 됩니다. 그리고 앞서 소개한 것처럼 데이터 채널 포트의 범위를 지정할 수 있는데, 별도로 지정하지 않는 경우는 1024 ~ 65535번의 포트를 사용하게 됩니다. 따라서 INBOUND 모두 허용이 필요하게 되는데, 서버 측에서 데이터 채널 포트 범위를 지정하여 특정 범위의 포트만 허용해주면 모든 포트 허용의 문제를 어느 정도 해결할 수 있습니다.
TIME_WAIT 상태에서는 RFC793에 정의대로라면 2MSL(Maximum Segment Lifetime), 즉 2분 동안 대기하게 됩니다.
그런데, 실제로 대부분의 OS에서는 최적화를 위해 1분 정도를 TIME_WAIT 상태에서 대기하도록 구현되어 있습니다.
리눅스도 이 시간을 1분으로 규정하고 있고, 변경이 불가능합니다. (커널 코드에 상수로 정해져 있습니다.)
그러나 TIME_WAIT가 과도하게 발생 된다고 하면 로컬 포트의 고갈을 유발 할 수 있다.
필자는 이 로컬 포트의 고갈로 인해서 발생한 문제이다.
먼저 가용한 포트 개수를 확인하자
약 28000 개의 포트를 사용할 수 있다고 한다.
cat /proc/sys/net/ipv4/ip_local_port_range
[root@yoon ~]# cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999
포트 고갈 테스트(정말 port 때문일까?)
귀신같이 NoRouteToHostException 가 나타났다
[root@yoon ~]# sysctl -w net.ipv4.ip_local_port_range="1024 1024"
net.ipv4.ip_local_port_range = 1024 1024
1.포트의 개수를 늘리자.
그러나 점점 포트의 개수가 증가하여 가득 채운다면 NoRouteToHostException가 재등장 할것이다.
[root@yoon ~]# sysctl -w net.ipv4.ip_local_port_range="1024 65535"
net.ipv4.ip_local_port_range = 1024 65535
2.reuse 옵션 활성화 여부를 확인 한 후 1로 설정 하여 reuse 옵션을 사용 한다.
tcp_tw_reuse 옵션을 활성화 하여 time_wait 되는 port 를 재사용할 수 있다.
tcp_tw_reuse 는 TIME_WAIT상태의 소켓중 timestamp 보다 작은 값의 timestamp 를 갖는 소켓을 재사용 한다. reuse 옵션 활성화 여부를 확인 한 후 1로 설정 하여 reuse 옵션을 사용 한다.
[root@yoon ~]# sysctl -a | grep reuse
net.ipv4.tcp_tw_reuse = 0
[root@yoon ~]# sysctl -w net.ipv4.tcp_tw_reuse="1"
net.ipv4.tcp_tw_reuse = 1
보완점
socket ConnectionPool을 도입하여 애플리케이션 단에서 해결해보자