NGINX 내부: 성능 및 확장성을 위한 설계 방법

관리자
조회수 337



NGINX는 웹 성능에서 선두를 달리고 있으며, 이는 모두 소프트웨어가 설계된 방식 덕분입니다. 

많은 웹 서버와 애플리케이션 서버가 간단한 스레드 또는 프로세스 기반 아키텍처를 사용하는 반면, 

NGINX는 정교한 이벤트 기반 아키텍처로 두드러지며, 이를 통해 최신 하드웨어에서 수십만 개의 동시 연결로 확장할 수 있습니다.

NGINX 아키텍처 인포그래픽은 상위 레벨 프로세스 아키텍처에서 드릴다운하여 

NGINX가 단일 프로세스 내에서 여러 연결을 처리하는 방식을 보여줍니다. 

이 블로그에서는 모든 것이 어떻게 작동하는지 자세히 설명합니다.


장면 설정 – NGINX 프로세스 모델


NGINX(및 NGINX Plus) 마스터 프로세스는 세 가지 유형의 자식 프로세스를 생성합니다. 워커, 캐시 관리 및 캐시 로더입니다. 이들은 캐싱, 세션 지속성, 속도 제한 및 로깅을 위해 공유 메모리를 사용했습니다.

이 디자인을 더 잘 이해하려면 NGINX가 어떻게 실행되는지 이해해야 합니다. 

NGINX에는 마스터 프로세스(구성 읽기 및 포트 바인딩과 같은 특권 작업을 수행)와 여러 개의 워커 및 헬퍼 프로세스가 있습니다.

# restart service nginx 
* restart nginx
# ps -ef -forest | grep nginx
root 32475 1 0 13:36 ? 00:00:00 nginx: Master process /usr/sbin/nginx
-c /etc/nginx/nginx.conf
nginx 32476 32475 0 13:36 ? 00:00:00 _ nginx: worker processes
nginx 32477 32475 0 13:36 ? 00:00:00 _ nginx: worker processes
nginx 32479 32475 0 13:36 ? 00:00:00 _ nginx: worker process
nginx 32480 32475 0 13:36 ? 00:00:00 _ nginx: worker process
nginx 32481 32475 0 13:36 ? 00:00:00 _ nginx: cache manager process
nginx 32482 32475 0 13:36 ? 00:00:00 _ nginx: cache loader process

이 4코어 서버에서 NGINX 마스터 프로세스는 4개의 워커 프로세스와 디스크상 콘텐츠 캐시를 관리하는 몇 개의 캐시 도우미 프로세스를 생성합니다.


아키텍처는 왜 중요한가?

모든 Unix 애플리케이션의 기본은 스레드 또는 프로세스입니다. (Linux OS 관점에서 스레드와 프로세스는 대부분 동일합니다. 가장 큰 차이점은 메모리를 공유하는 정도입니다.) 스레드 또는 프로세스는 운영 체제가 CPU 코어에서 실행되도록 예약할 수 있는 독립적인 명령어 집합입니다. 대부분의 복잡한 애플리케이션은 두 가지 이유로 여러 스레드 또는 프로세스를 병렬로 실행합니다.

  • 동시에 더 많은 컴퓨팅 코어를 사용할 수 있습니다.
  • 스레드와 프로세스를 사용하면 병렬 작업을 매우 쉽게 수행할 수 있습니다(예: 여러 연결을 동시에 처리).

프로세스와 스레드는 리소스를 소비합니다. 각각 메모리와 다른 OS 리소스를 사용하고 코어에서 스왑해야 합니다(컨텍스트 스위치라고 하는 작업). 대부분의 최신 서버는 수백 개의 작고 활성적인 스레드나 프로세스를 동시에 처리할 수 있지만 메모리가 고갈되거나 높은 I/O 부하로 인해 대량의 컨텍스트 스위치가 발생하면 성능이 심각하게 저하됩니다.

네트워크 애플리케이션을 설계하는 일반적인 방법은 각 연결에 스레드나 프로세스를 할당하는 것입니다. 이 아키텍처는 간단하고 구현하기 쉽지만 애플리케이션이 수천 개의 동시 연결을 처리해야 할 때 확장되지 않습니다.


NGINX는 어떻게 작동하나요?

NGINX는 사용 가능한 하드웨어 리소스에 맞춰 조정된 예측 가능한 프로세스 모델을 사용합니다.

  • 마스터 프로세스는 구성 읽기, 포트 바인딩과 같은 특권 작업을 수행한 후 소수의 자식 프로세스(다음 세 가지 유형)를 생성합니다.
  • 캐시 로더 프로세스는 시작 시 디스크 기반 캐시를 메모리에 로드하기 위해 실행된 다음 종료됩니다. 보수적으로 예약되므로 리소스 요구 사항이 낮습니다.
  • 캐시 관리자 프로세스는 주기적으로 실행되어 디스크 캐시에서 항목을 정리하여 구성된 크기 내로 유지합니다.
  • 워커 프로세스는 모든 작업을 수행합니다! 이들은 네트워크 연결을 처리하고, 디스크에 콘텐츠를 읽고 쓰고, 업스트림 서버와 통신합니다.

대부분의 경우 권장되는 NGINX 구성(CPU 코어당 하나의 워커 프로세스 실행)은 하드웨어 리소스를 가장 효율적으로 사용합니다. worker_processes 지시문에서 auto 매개변수를 설정하여 구성합니다.

worker_processes 자동;

NGINX 서버가 활성화되면 작업자 프로세스만 바쁩니다. 

각 작업자 프로세스는 비차단 방식으로 여러 연결을 처리하여 컨텍스트 전환 횟수를 줄입니다.

각 워커 프로세스는 단일 스레드이며 독립적으로 실행되어 새로운 연결을 잡아서 처리합니다. 

프로세스는 공유 캐시 데이터, 세션 지속성 데이터 및 기타 공유 리소스에 대해 공유 메모리를 사용하여 통신할 수 있습니다.


NGINX Worker 프로세스 내부



NGINX 워커 프로세스는 웹 클라이언트의 요청을 처리하기 위한 비차단, 이벤트 기반 엔진입니다.


각 NGINX 워커 프로세스는 NGINX 구성으로 초기화되고 마스터 프로세스로부터 일련의 수신 소켓을 제공받습니다.

NGINX 워커 프로세스는 리스닝 소켓(accept_mutex 및 커널 소켓 샤딩)에서 이벤트를 기다리는 것으로 시작합니다. 

이벤트는 새로운 수신 연결에 의해 시작됩니다. 이러한 연결은 상태 머신에 할당됩니다. 

HTTP 상태 머신이 가장 일반적으로 사용되지만 NGINX는 스트림(원시 TCP) 트래픽과 

여러 메일 프로토콜(SMTP, IMAP 및 POP3)에 대한 상태 머신도 구현합니다.



NGINX는 들어오는 클라이언트 요청을 처리하기 위해 HTTP 헤더를 읽고, 구성된 경우 제한을 적용하고, 필요에 따라 내부 리디렉션 및 하위 요청을 만들고, 백엔드 서비스로 전달하고, 필터를 적용하고, 작업을 기록합니다.

상태 머신은 본질적으로 NGINX에 요청을 처리하는 방법을 알려주는 명령어 집합입니다. 

NGINX와 동일한 기능을 수행하는 대부분의 웹 서버는 유사한 상태 머신을 사용합니다. 차이점은 구현에 있습니다.


상태 머신 스케줄링

상태 머신을 체스 규칙처럼 생각해보세요. 각 HTTP 트랜잭션은 체스 게임입니다. 체스판의 한쪽에는 웹 서버, 즉 매우 빠르게 결정을 내릴 수 있는 그랜드마스터가 있습니다. 다른 쪽에는 원격 클라이언트, 즉 비교적 느린 네트워크를 통해 사이트나 애플리케이션에 액세스하는 웹 브라우저가 있습니다.

그러나 게임 규칙은 매우 복잡할 수 있습니다. 예를 들어, 웹 서버는 다른 당사자와 통신해야 할 수도 있고(업스트림 애플리케이션으로 프록시) 인증 서버와 통신해야 할 수도 있습니다. 웹 서버의 타사 모듈은 게임 규칙을 확장할 수도 있습니다.


차단 상태 머신

프로세스 또는 스레드를 운영 체제가 CPU 코어에서 실행되도록 예약할 수 있는 자체 포함형 명령어 집합으로 설명한 것을 기억하세요. 대부분의 웹 서버와 웹 애플리케이션은 체스 게임을 하기 위해 연결당 프로세스 또는 연결당 스레드 모델을 사용합니다. 각 프로세스 또는 스레드에는 한 게임을 끝까지 플레이하기 위한 명령어가 들어 있습니다. 프로세스가 서버에서 실행되는 동안 대부분의 시간을 '차단'된 상태로 보냅니다. 즉, 클라이언트가 다음 움직임을 완료할 때까지 기다립니다.


대부분의 웹 애플리케이션 플랫폼은 차단 I/O를 사용합니다. 즉, 각 작업자(스레드나 프로세스)는 한 번에 하나의 활성 연결만 처리할 수 있습니다.

웹 서버 프로세스는 수신 소켓에서 새로운 연결(클라이언트가 시작한 새로운 게임)을 수신합니다. 새로운 게임이 생기면 해당 게임을 플레이하고, 각 움직임마다 블로킹을 하여 클라이언트의 응답을 기다립니다. 게임이 완료되면 웹 서버 프로세스는 클라이언트가 새 게임을 시작할지 기다릴 수 있습니다(이것은 keepalive 연결에 해당). 연결이 닫히면(클라이언트가 사라지거나 시간 초과가 발생하면) 웹 서버 프로세스는 새 게임을 수신 대기합니다.

기억해야 할 중요한 점은 모든 활성 HTTP 연결(모든 체스 게임)에는 전용 프로세스 또는 스레드(그랜드마스터)가 필요하다는 것입니다. 이 아키텍처는 간단하고 타사 모듈('새로운 규칙')로 확장하기 쉽습니다. 그러나 엄청난 불균형이 있습니다. 파일 설명자와 소량의 메모리로 표현되는 다소 가벼운 HTTP 연결은 별도의 스레드 또는 프로세스, 매우 무거운 운영 체제 객체에 매핑됩니다. 프로그래밍 편의성이지만 엄청나게 낭비적입니다.

NGINX는 진정한 그랜드마스터입니다

동시 시범 경기에 대해 들어보셨나요? 한 명의 체스 그랜드마스터가 수십 명의 상대와 동시에 경기를 하는 경기입니다.

키릴 게오르기에프키릴 게오르기예프는 불가리아 소피아에서 360명과 동시에 경기를 펼쳤습니다. 

그의 최종 성적은 284승 70무 6패였습니다.

이것이 NGINX 워커 프로세스가 "체스"를 두는 방식입니다. 

각 워커는 수백(사실, 수십만) 개의 게임을 동시에 할 수 있는퍼포먼스를 보여주고 있습니다.

NGINX는 비차단 I/O를 갖춘 이벤트 기반 아키텍처를 사용하므로 수십만 개의 연결을 동시에 처리할 수 있습니다.

워커는 청취 및 연결 소켓에서 이벤트를 기다립니다. 소켓에서 이벤트가 발생하고 워커가 이를 처리합니다. 청취 소켓의 이벤트는 클라이언트가 새로운 체스 게임을 시작했다는 것을 의미합니다.  워커는 새로운 연결 소켓을 만듭니다. 연결 소켓의 이벤트는 클라이언트가 새로운 움직임을 했다는 것을 의미합니다. 워커는 즉시 응답합니다.

워커는 네트워크 트래픽을 차단하지 않고 "상대방"(클라이언트)이 응답할 때까지 기다립니다. 이동을 마치면 워커는 즉시 이동이 처리되기를 기다리는 다른 게임으로 이동하거나 새로운 플레이어를 문으로 맞이합니다.


이것이 차단형 다중 프로세스 아키텍처보다 빠른 이유는 무엇일까요?

NGINX는 워커 프로세스당 수십만 개의 연결을 지원하도록 매우 잘 확장됩니다. 

각 새 연결은 다른 파일 설명자를 만들고 워커 프로세스에서 소량의 추가 메모리를 소모합니다. 연결당 추가 오버헤드는 매우 적습니다. 

NGINX 프로세스는 CPU에 고정된 상태로 유지될 수 있습니다. 컨텍스트 전환은 비교적 드물며 수행할 작업이 없을 때 발생합니다.

차단형, 프로세스당 연결 방식에서는 각 연결에 많은 양의 추가 리소스와 오버헤드가 필요하며 

컨텍스트 전환(한 프로세스에서 다른 프로세스로의 전환)이 매우 빈번하게 발생합니다.

더 자세한 설명은 NGINX, Inc.의 기업 개발 담당 부사장이자 공동 창립자인 Andrew Alexeev가 쓴 NGINX 아키텍처에 관한 기사를 참조하세요.

적절한 시스템 튜닝을 통해 NGINX는 작업자 프로세스당 수십만 개의 동시 HTTP 연결을 처리할 수 있도록 확장 가능하며, 

흐름이 끊기지 않고 트래픽 급증(새로운 게임의 유입)을 흡수할 수 있습니다.

구성 업데이트 및 NGINX 업그레이드

NGINX의 프로세스 아키텍처는 작업자 프로세스 수가 적어 구성은 물론 NGINX 바이너리 자체의 업데이트도 매우 효율적으로 이루어집니다.

NGINX는 다운타임(요청 처리 중단) 없이 구성을 다시 로드합니다.

NGINX 구성 업데이트는 매우 간단하고 가볍고 안정적인 작업입니다. 

일반적으로 nginx -s reload 명령을 실행하면 되는데, 

이 명령은 디스크에서 구성을 확인하고 마스터 프로세스에 SIGHUP 신호를 보냅니다.

마스터 프로세스가 SIGHUP를 수신하면 두 가지 작업을 수행합니다.

  1. 구성을 다시 로드하고 새로운 작업자 프로세스 세트를 포크합니다. 이러한 새로운 작업자 프로세스는 즉시 연결을 수락하고 트래픽을 처리하기 시작합니다(새로운 구성 설정을 사용).
  2. 이전 작업자 프로세스에 우아하게 종료하라는 신호를 보냅니다. 작업자 프로세스는 새 연결을 수락하지 않습니다. 현재 HTTP 요청이 완료되는 즉시 작업자 프로세스는 연결을 깔끔하게 종료합니다(즉, 지속되는 keepalive가 없습니다). 모든 연결이 닫히면 작업자 프로세스가 종료됩니다.


이 재로드 프로세스는 CPU 및 메모리 사용량에 약간의 급증을 일으킬 수 있지만 일반적으로 활성 연결의 리소스 부하에 비하면 감지할 수 없습니다. 초당 여러 번 구성을 재로드할 수 있습니다(그리고 많은 NGINX 사용자가 정확히 그렇게 합니다). 매우 드물게는 연결이 닫힐 때까지 기다리는 여러 세대의 NGINX 작업자 프로세스가 있을 때 문제가 발생하지만 이러한 문제도 빠르게 해결됩니다.

NGINX의 바이너리 업그레이드 프로세스는 고가용성이라는 성배를 달성합니다. 

즉, 연결 끊김, 가동 중지 또는 서비스 중단 없이 즉시 소프트웨어를 업그레이드할 수 있습니다.

NGINX는 다운타임(요청 처리 중단) 없이 바이너리를 다시 로드합니다.


바이너리 업그레이드 프로세스는 구성의 우아한 재로드와 접근 방식이 비슷합니다. 

새로운 NGINX 마스터 프로세스는 원래 마스터 프로세스와 병렬로 실행되며, 수신 소켓을 공유합니다. 

두 프로세스 모두 활성화되어 있으며, 해당 작업자 프로세스가 트래픽을 처리합니다. 

그런 다음 이전 마스터와 해당 작업자에 우아하게 종료하도록 신호를 보낼 수 있습니다.

전체 프로세스는 NGINX 제어에 더 자세히 설명되어 있습니다.



결론

NGINX 아키텍처 인포그래픽은 NGINX의 기능에 대한 개략적인 개요를 제공하지만, 

이 간단한 설명 뒤에는 10년이 넘는 혁신과 최적화가 숨어 있습니다. 

이를 통해 NGINX는 현대 웹 애플리케이션에 필요한 보안과 안정성을 유지하면서도 다양한 하드웨어에서 최상의 성능을 제공할 수 있습니다.

NGINX의 최적화에 대해 더 자세히 알아보려면 다음과 같은 유용한 리소스를 확인하세요.



위 내용와 같이 NGINX Plus를 활용하여 Demo 가 필요하시면 하단의 전문가에게 상담받기 버튼을 클릭해주세요