TIME_WAIT 소켓
1. TIME_WAIT 소켓 문제점
# active closer : 먼저 연결을 끝는 쪽
# passive closer : 상대방이 연결을 끝는 쪽
# 연결을 끝는게 중요한 이유는 active closer 쪽에 TIME_WAIT 소켓이 생성되기 때문
# TIME_WAIT 소켓 확인
$ netstat -napo | grep -i time_wait | wc -l
44
# TIME_WAIT 소켓이 많아지면 로컬포트 고갈에 따른 애플리케이션 타임아웃이 발생
# "net.ipv4.ip_local_port_range" 파라미터는 외부와 통신하기 위해 필요한 로컬포트의 범위를 지정
# 로컬포트가 고갈되는 현상을 막기위해 대부분의 애플리케이션에서는 Connection Pool과 같은 방식을 사용해서
# 한번 맺어 놓은 TCP 연결을 계속해서 재사용할 수 있게 구현하고 있다.
# net.ipv4.ip_local_port_range 범위 확인
$ sysctl -a | grep -i net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999
2. 클라이언트에서의 TIME_WAIT
# TIME_WAIT은 서버에 생기는 것이 아니라 먼저 연결을 끊는 쪽에서 생긴다.
# HTTP 기반의 서비스는 대부분 서버가 먼저 연결을 끊는 경우가 많아서 서버에서 TIME_WAIT 소켓이 발생
# 한다고 오해할 수 있지만, 그렇지 않다는 것도 꼭 기억해야함.
1번 2번
USER ----------> WEB Server -----------> DB Server
POST/data HTTP DB에 저장
# 1번 과정 : 클라이언트 = USER, 서버 = WEB Server
# 2번 과정 : 클라이언트 = WEB Server, 서버 = DB Server
# 참고) 통신하는 과정에 따라 서버의 역햘을 했던 서버는 반대로 클라이언트 역할을 하기도 한다.
# 그리고 이 과정에서 클라이언트 역할을 하는 서버가 먼저 연결을 끊는다면 클라이언트 입장의 TIME_WAIT
# 소켓이 발생할 수 있다. 클라이언트 입장에서 문제점은 TIME_WAIT이 발생했을 때 로컬포트가 고갈되는 것이 문제이다.
# TIME_WAIT 생성 테스트
$ curl http://www.kakao.com > /dev/null
$ netstat -napo | grep 80
tcp 0 0 172.16.26.249:34806 169.254.169.254:80 TIME_WAIT - timewait (25.29/0/0)
# src IP : 172.16.26.249
# dest IP : 169.254.169.254
# 클라이언트가 "www.kakao.com"에 GET 요청을 할 때 이 요청을 처리 할 소켓이 필요한데(172.16.26.249:34806)
# 을 만들어 처리한 걸 알 수 있다. 이것은 TIME_WAIT 소켓상태가 풀릴 떄까지 해당 소켓을 사용할 수 없다는 뜻임.
# 문제점 : TIME_WAIT 소켓이 많이 생성되어 로컬포트를 사용할 수 없을때 "connection refused" 가 발생할 수 있음.
2.1 net.ipv4.tcp_tw_reuse
# 1. 로컬 포트 고갈에 대응할 수 있는 방법은 해당 커널 파라미터를 이용하는 방법
# net.ipv4.tcp_tw_reuse : TIME_WAIT 소켓을 처리하는 커널 파라미터로 외부로 요청할 때 TIME_WAIT 소켓을
# 재사용 할 수 있게 해준다.
2.2 TIME_WAIT 소켓 재사용 테스트
# 1. net.ipv4.tcp_tw_reuse = 0 일때
$ curl http://www.kakao.com > /dev/null
$ curl http://www.kakao.com > /dev/null
$ netstat -napo | grep 80
tcp 0 0 172.16.26.249:35510 169.254.169.254:80 TIME_WAIT - timewait (55.70/0/0)
tcp 0 0 172.16.26.249:44594 211.249.221.27:80 TIME_WAIT - timewait (6.23/0/0)
# local_port_range 값에 있는 포트넘버를 임의로 선정하여 소켓에 할당함, 이때 로컬 포트가 부족하면
# 통신할 수 없게 된다.
# 2. net.ipv4.tcp_tw_reuse = 1 일때
$ curl http://www.kakao.com > /dev/null
$ curl http://www.kakao.com > /dev/null
$ netstat -napo | grep 80
tcp 0 0 172.16.26.249:35960 169.254.169.254:80 TIME_WAIT - timewait (11.82/0/0)
# local_port를 1개를 재사용하여 사용중이므로 GET 요청을 계속해도 로컬포트 "35960" 으로 계속
# 통신을 이어나가게 된다.
# 참고) net.ipv4.tcp_tw_reuse = 1 로 설정 시
# net.ipv4.tcp_timestamps = 값도 "1" 이어야한다.
2.3 Connections Pool 방식 사용하기
# 로컬포트를 재사용하면 TIME_WAIT 상태의 소켓을 재사용할 수 있어, 로컬 포트가 고갈되어 발생할 수 있는
# 장애는 처리할 수 있지만, 근본적인 문제 해결방법으로 "Connection Pool" 방식으로 해결하는 방법도 있다.
# 1. Connection Less
WEB Server -------------------------> DB Server
------------------------->
------------------------->
------------------------->
매번 소켓 연결이 필요
- 1번 방식은 HTTP가 많이 사용하는 방식, 요청할 때마다 소켓을 새로 연결하는 방식
# 2. Connection Pool
WEB Server -------------------------> DB Server
소켓을 미리 열어놓고 사용
- 2번 방식은 소켓을 미리 열어놓기 때문에 불필요한 TCP 연결 맺기/끊기 과정이 없어 더 좋은 애플리케이션
응답 속도를 구현할 수 있다.
- 초당 수십, 수백개의 요청이 들어오는 대규모 시스템이라면 Connection Pool 방식을 사용하여 응답속도
를 향상 시킬 수 있어서 사용하면 좋다.
2.4 net.ipv4.tcp_tw_recycle
# net.ipv4.tw_reuse 는 나갈 때 사용하는 로컬 포트에서 TIME_WAIT 상태의 소켓을 재사용할 수 있게
# 해주는 파라미터
# net.ipv4.tcp_tw_recycle 은 서버 입장에서 TIME_WAIT 상태의 소켓을 빠르게 회수하고 재활용할 수
# 있게 해주는 파라미터이다.
# TIME_WAIT 소켓의 기본 타임아웃은 1분이다.
# net.ipv4_tw_recycle 수정방법
$ sysctl -w "net.ipv4.tcp_tw_recycle=1"
# 참고) AWS EC2는 해당 파라미터가 없다.
# 웹서버에서는 net.ipv4_tw_recycle을 켜서는 안된다.
# 웹서버에서는 SYN 패킷이 버려지는 문제가 발생할 수 있기 때문에 권하지 않는다.
2.5 keepalive 사용하기
# keepalive : 한번 맺은 세션을 요청이 끝나더라도 유지해주는 기능
# ex) 2초 간격으로 GET 요청이 들어온다면 2초마다 한 번씩 세션을 맺기보다는 하나의 세션을 연결해 놓고
# 그 연결을 유지하면서 지속적으로 요청을 처리하는 것이 서비스 응답 속도 측면에서 도움이된다.
# nginx.conf 에서 keepalive 수정방법
$ sudo vi /etc/nginx.conf
keepalive_timeout 0;
# keepalive_timeout 0 일때 새로운 요청마다 새로운 소켓을 열어야 한다.
# 만약 10번의 GET 요청이 있다면 TIME_WAIT 소켓은 10개가 될 것이다.
# keepalive를 켜서 해당 TIME_WAIT 소켓의 생성을 줄일 수 있다면 TCP 연결 맺기/끊기 작업이
# 없어지면서 응답속도가 빨라진다.
$ sudo vi /etc/nginx.conf
keepalive_timemout 10;
# 위 방식은 10초가 지나야만 서버에서 먼저 연결을 끊게 된다. 이런식으로 서버의 입장에서 keepalive
# 를 켜서 세션을 유지해주면 TIME_WAIT 소켓을 줄일 수 있으며, 서비스의 응답 속도도 향상시킬 수 있다.
3. case study - nginx upstream에서 발생하는 TIME_WAIT
# JAVA 기반으로 서비스를 개발할 때 앱 서버를 보통 tomcat 또는 netty를 사용하게 된다.
# 앞단에 웹서버(nginx, apache)를 두고 구성하는 경우가 많다.
# 이때 앞단 웹서버와 뒷단 앱서버 간에 keepalive를 적용하지 않은 채 서비스하게 되면 웹 서버와 앱 서버
# 사이 TIME_WAIT이 발생한다.
# (요청 시)
# 문제점 1. 웹서버 ---------> 앱서버, 로컬 포트 고갈이 일어날 수 있음.
# (로컬포트 사용)
# 해결책 : net.ipv4.tcp_tw_reuse = 1 을 사용하여 해결할 수 있음.
# 문제점 2. 불필요한 TCP 연결 맺기/끊기 과정으로 인해 불필요한 성능 낭비 및 서비스 응답속도 지연
TCP Keepalive
1. TCP Keepalive란?
# TCP 통신 간 불필요한 3way-handshake를 방지하기 위해 지속적으로 이루어지게 만들어 놓기 위한 방식
# netstat를 이용해서 TCP Keepalive 타이머 확인하기
$ netstat -napo
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name Timer
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN - off (0.00/0/0)
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN - off (0.00/0/0)
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN - off (0.00/0/0)
tcp 0 0 172.16.26.249:22 172.16.129.36:52138 ESTABLISHED - keepalive (1383.02/0/0)
# sshd 데몬이 사용하는 소켓에 keepalive 옵션이 켜져있고 타이머가 1383초가량 남아있다. 해당 타이머
# 의 시간이 다 되면 연결이 살아있는지 확인하는 작은 패킷을 하나 보낸다.
1.2 TCP Keepalive 사용방법
# Redis 서버 구축 후 테스트진행(터미널 2개 열어서 명령어 따로 진행)
# redis 소켓 생성
$ telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
# redis 소켓 확인
$ sudo netstat -napo | grep -i 6379 | grep -i est
tcp 0 0 127.0.0.1:6379 127.0.0.1:46850 ESTABLISHED 3724/redis-server 1 keepalive (280.01/0/0)
tcp 0 0 127.0.0.1:46850 127.0.0.1:6379 ESTABLISHED 3781/telnet off (0.00/0/0)
# 기본적으로 keepalive 시간은 300초로 잡혀져 있다.
# redis TCP keepalive 시간변경 방법
$ redis-cli
127.0.0.1:6379> config set tcp_keepalive 100
OK
127.0.0.1:6379> config get tcp_keepalive 100
1) "tcp-keepalive"
2) "100"
# TCP keepalive 시간 확인하기
$ netstat -napo | grep -i 6379 | grep -i est
tcp 0 0 127.0.0.1:6379 127.0.0.1:46866 ESTABLISHED 3724/redis-server 1 keepalive (99.80/0/0)
tcp 0 0 127.0.0.1:46866 127.0.0.1:6379 ESTABLISHED 3866/telnet off (0.00/0/0)
# config set으로 keepalive 시간을 100초로 변경된 것을 확인할 수 있다.
1.3 TCP keepalive 커널 파라미터
# net.ipv4.tcp_keepalive_time : keepalive 패킷을 보낼 최대 전송횟수를 정의
# net.ipv4.tcp_keepalive_probes : 최대 재전송 횟수를 정의
# net.ipv4.tcp_keepalive_intvl : 재전송 패킷을 보내는 주기를 의미
# TCP keepalive 커널 파라미터 설정값 확인방법
$ sysctl -a | grep -i "net.ipv4.tcp_keepalive"
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200
# TCP keepalive 타이머 변경방법
$ sysctl -w net.ipv4.tcp_keepalive_time = 120