TIME_WAIT & TCP Keepalive

EnoSoup·2021년 7월 26일
0

Linux

목록 보기
14/15
post-thumbnail

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
profile
Cloud Engineer@Plateer. 클라우드 상에서 엔지니어링을 재미있게 하는 엔지니어입니다.

0개의 댓글