0x0102

DevOps - TCP Keepalive를 이용한 세션 유지 본문

Study/DevOps

DevOps - TCP Keepalive를 이용한 세션 유지

jxx_yxjx 2024. 2. 1. 15:38

1. TCP Keepalive란?

TCP 통신을 위해서는 반드시 3way handshake가 필요하지만 통신량이 많고 지속적인 경우 불편함을 겪을 수 있다.

이 때 두 종단 간 맺은 세션을 유지해서 통신이 일어날 때마다 유지 중인 세션을 이용하는 방식인 TCP Keepalive를 생각할 수 있다.

 

TCP Keepalive 환경에서의 패킷 흐름은 3 way handshake > 요청과 응답 > keepalive > ack > 요청 > 응답 순과 같다.

 

TCP Keepalive는 연결된 두 세션이 살아있는지 확인하는 아주 작은 패킷을 하나 보내며 실행된다.

이 때 양쪽 모두 이 패킷을 보낼 필요는 없으며 둘 중 어느쪽이라도 이 기능을 사용하면 세션은 유지된다.

 

현재 사용하고 있는 네트워크 소켓이 Keepalive를 지원하고 있는지는  netstat 명령으로 알아볼 수 있다.

Timer : 현재 소켓에 설정된 타이머 값

 

TCP Keepalive 사용법

- 소켓 생성 시 소켓 옵션을 setsocketopt()함수를 통해 설정. 함수 옵션 중 S0_KEEPALIVE 선택

- Redis 인스턴스 사용. redis-cli

 

2. TCP Keepalive의 파라미터들

커널에서는 TCP Keepalive를 유지하기 위해 세가지 커널 파라미터를 제공한다.

 

intvl : keepalive 재전송 패킷을 보내는 주기

probes : keepalive 패킷을 보낼 최대 전송 횟수 정의

time : keepalive 소켓 유지 시간

즉, time초 동안 기다린 후 keepalive 확인 패킷을 보내고, 최초 패킷에 대한 응답이 오지 않으면 intvl 간격으로 probes번 패킷을 더 보내는 것이다.

 

3. TCP Keepalive와 좀비 커넥션

TCP Keepalive는 커널 레벨에서 두 종단 간의 연결을 유지하며 불필요한 TCP handshake를 줄여 서비스의 품질을 높인다.

그러나 더 큰 장점은 잘못된 커넥션, 즉 좀비 커넥션이라고 부르는 소켓을 방지하는 것이다.

 

실습

1. tcp handshake

2. 마지막 통신에 대한 ack

3. 마지막 ack 후 keepalive 확인 패킷 전송

4. 3의 패킷에 응답을 받지 못했으므로 intvl 시간만큼 경과 후 한 번 더 전송

5. 4의 패킷에 응답을 받지 못했음으로 한 번 더 전송. probes 횟수만큼 보냈으므로 마지막 전송이 됨.

6. keepalive에 대한 확인 패킷을 정해진 횟수만큼 보낸 후에도 응답이 없으므로 클라이언트는 연결이 끊어졌다고 인지하고 서버에 RST 패킷을 보낸 후 소켓 정리

 

-> 이로써 애플리케이션에서 직접 연결을 주기적으로 확인하는 로직을 추가하지 않고도 커널레벨에서 커넥션 관리를 할 수 있다.

 

4. TCP Keepalive와 HTTP Keepalive

TCP Keepalive:  두 종단 간 연결 유지 목적

HTTP Keepalive: 최대한 연결을 유지하는 것이 목적

ex) 두 값 모두 60일 때 tcp는 60초 간격으로 연결을 확인하지만 http는 60초 동안 유지하고, 후에도 요청이 없다는 연결을 해지한다.

 

TCP Keepavlie가 설정되어 있어도 HTTP Keepalive가 설정되어 있다는 해당 값에 맞춰 동작한다.

즉, HTTP 값을 기준으로 의도한대로 동작하므로 두 값이 달라도 괜찮다.

 

6. Case Study - MQ 서버와 로드밸런서

- 서비스 중인 서버에서 발생한 이슈를 TCP Keepalive로 해결한 경우

 

흔히 비동기식 작업 처리를 위해 MQ(Message Queue) 서버를 사용한다.

이 때 서버의 이중화를 위한 방법으로 로드밸런서가 있다.

 

클라이언트는 클러스터링으로 여러 서버를 사용할 수 있지만, 결국 서버 한대를 골라야하고 해당 서버에 장애가 발생했다면 회피할 수 있어야한다.

그러나 로드밸런서 밑에 MQ 서버들을 둔 상태로 클러스터링을 맺는다면 로드밸런서 VIP를 통해 서버에 붙으면 되므로 MQ 서버 중 몇대에 문제가 생겨도 회피하지 않아도 된다.

 

이러한 MQ 서버에도 문제는 존재한다.

바로 1) 클라이언트에서 간헐적으로 타임아웃이 발생하는 것과 2) 서버에서 사용하지 않는 소켓이 ESTABLISHED 상태로 유지되는 것이다.

이러한 이유를 tcpdump로 확인해보니 타임아웃 순간에 클라이언트에서 발송한 패킷들에 대해 RST로 응답 패킷이 온 것을 알 수 있었다.

+ RST 패킷 : TCP Handshake 없이 바로 데이터 전송 패킷이 전송되는 등 통신 규약을 지키지 않았을 때 발송하는 패킷

 

잘 연결되어 있는 소켓에서 왜 RST 응답이 왔을까?

바로 로드밸런서의 Idle timeout 때문이다.

 

서버가 로드밸런서 밑에 있기 때문에 클라이언트와 서버가 통신할 때 로드밸런서를 거치게 되는데

서버 입장에서는 요청이 들어오는 패킷이 TCP handshake도 맺지 않은 상대로부터 온 것이라고 생각하고 RST 패킷을 보내는 것이다.

RST 패킷을 받으면 다시 TCP handshake를 맺어야하고 요청을 보내야하는데 이 과정에서 Timeoiut Exception 도 경험하게 된다.

 

서비스 특성상 사용자 요청이 적은 새벽 시간, 맺어져 있는 세션으로 패킷이 흐르지 않을 가능성이 크기 때문에 idle timeout에 걸려 커넥션 풀로 열어놓은 세션들이 로드밸런서에서 지워지는 일이 발생한다.

이후 재연결을 위해 클라이언트가 요청을 보내면 위와 같이 RST 패킷이 오는 경우가 발생하고, 클라이언트는 타임아웃을 경험한다.

클라이언트는 자신의 커넥션 풀이 잘못되었음을 인지하고 새로운 커넥션을 열어 새로운 서버와 연결한다.

 

문제는 서버가 이러한 상황을 알 수 없어 좀비 커넥션이 남는 것이다.

(-> 이러한 상황은 로드밸런서의 Idle timeout에 걸리지 않도록 keepalive 파라미터를 수정함으로써 해결할 수 있다.)