TCP, 웹서버, HTTP 관점에서 keep-alive란 무엇인지 알아보고, HTTP keep-alive 통신을 하는 클라이언트 입장에서 주의해야 할 사항에 대해서도 함께 알아보겠습니다.
서버와 클라이언트가 맺은 연결을 유지하는 방식을 의미합니다.
'연결을 얼마나 유지할지'와 같은 keep-alive 속성은 두 곳에서 관리가 됩니다. 운영체제 커널에서 관리되는 TCP keep-alive, 웹 서버에서 관리되는 웹 서버 keep-alive 이렇게 두 가지가 있습니다.
HTTP와 같은 일반적인 웹 서버 통신은 웹 서버의 keep-alive의 속성을 따릅니다. 반면에 Rabbit MQ, Active MQ, Thrift RPC 등 TCP 통신은 TCP keep-alive 속성을 따릅니다.
자신이 운영하는 서버가 TCP keep-alive의 속성을 따르고 있는지, 웹 서버의 keep-alive 속성을 따르고 있는지 알아야 합니다.
TCP keep-alive, 웹 서버 keep-alive의 차이에 대해 알아보겠습니다.
TCP keep-alive를 이용하는 경우에는 운영체제 커널에서 관리합니다.
운영체제 커널에서 관리되는 keep-alive 옵션의 종류는 다음과 같습니다.
> sysctl -a | grep keepalive
net.ipv4.tcp_keepalive_timeout = 10
net.ipv4.tcp_keepalive_probes = 20
net.ipv4.tcp_keepalive_intvl = 30
연결이 맺어진 뒤 10초(timeout) 동안 기다립니다. 그러곤 해당 커넥션이 살아있는지 확인합니다. 살아있다면 연결을 유지하지만 응답이 없다면 30초(intvl) 뒤에 다시 살아있는지 확인합니다. 최대 20번(probes)까지 살아있는지 확인을 시도합니다.
TCP 레벨에서의 keep-alive 옵션은 데드컨넥션을 제거하는데 효과적입니다. 클라이언트에서 서버로 연결 종료(FIN) 요청을 보내지 않고 자체적으로 커넥션을 제거한다면 서버에선 이를 알 방법이 없습니다. 따라서 서버에선 해당 커넥션을 계속 들고있는 문제가 발생합니다.
웹 서버를 이용하는 경우에는 웹 서버가 자체적으로 관리하는 keep-alive 옵션을 이용합니다. 웹 서버의 keep-alive를 이용하는 경우에는 TCP keep-alive의 속성을 무시합니다.
Apache 서버와 같은 요청당 하나의 스레드를 할당하는 블로킹 구조에서는 맺어진 소켓별로 서버의 max_threads(max_clients)가 늘어납니다.
Nginx와 같은 논블로킹 구조에서는 그렇지 않습니다.
Nginx 기준으로 keep-alive 옵션은 다음과 같습니다.
keepalive_time = 10s // 한 번 맺어진 연결을 10초간 유지
keepalive_max = 10 // 한 번 맺어진 연결을 통해 최대 10번의 요청 가능
HTTP는 웹 서버 통신이기 때문에 TCP keep-alive가 아닌 웹 서버의 keep-alive 속성을 따릅니다.
HTTP는 일반적으로 Connectionless 방식으로 매 요청마다 연결을 새롭게 맺습니다. 요청이 빈번하게 발생한다면 매번 연결을 새롭게 맺는 방식은 비효율적입니다.
그래서 HTTP는 keep-alive 헤더를 제공합니다. 이 헤더를 이용해 keep-alive를 이용할지 말지 선택할 수 있습니다.
HTTP 1.0 에서는 헤더에 keep-alive 옵션을 추가해줘야 동작합니다.
Connection: Keep-alive // 연결 유지
Connection: close // 연결 종료
HTTP 1.1 부터는 keep-alive가 기본 값으로 설정되어 있습니다. 별도 설정 없이 Keep-alive 옵션을 이용 가능합니다.
다만, 서버측의 웹 서버에 keep-alive 설정이 켜 있는 경우에만 동작을 합니다. 서버측의 웹 서버에 keep-alive 옵션이 켜져있지 않다면, 동작하지 않습니다.
HTTP Client 입장에서 주의해야할 사항이 있습니다.
서버측에선 커넥션을 종료했는데 클라이언트에서 해당 커넥션을 제거하지 않은 상황에 해당 커넥션을 다시 이용하려고 시도하는 경우 문제가 될 수 있습니다.
이것을 half-closed connection이라 부릅니다.
Apache HTTP Client를 이용하는 경우에는 NoHttpResponseException을 받을 수 있습니다.
Netty를 이용하는 경우에는 PrematureCloseException을 받을 수 있습니다.
이 것을 해결하기 위해서 클라이언트에서는 서버에 설정된 keepalive_time보다 짧게 connection의 evict time(life time)을 설정해주어야 합니다.
Apache HTTP Client Connection Pool을 이용하는 경우에는 evictIdleConnections 옵션을 통해 컨트롤 할 수 있습니다.