확장성

확장성이라는 용어는 종종 오용됩니다. 이 섹션에서는 이중 정의가 제공됩니다.

  • 확장성은 다중 프로세서 시스템(2, 4, 8, 32 이상 프로세서)에서 사용 가능한 처리 능력을 완전히 활용하는 기능입니다.
  • 확장성은 많은 수의 클라이언트를 서비스하는 기능입니다.

이러한 두 가지 관련 정의를 일반적으로 스케일 업이라고 합니다. 이 항목의 끝부분에서는 스케일 아웃에 대한 팁을 제공합니다.

이 설명에서는 확장 가능한 서버가 더 일반적인 요구 사항이므로 확장 가능한 클라이언트가 아닌 확장 가능한 서버를 작성하는 데만 중점을 둡니다. 이 섹션에서는 RPC 및 RPC 서버의 컨텍스트에서만 확장성을 다룹니다. 경합 감소, 글로벌 메모리 위치에서 자주 캐시 누락 방지 또는 거짓 공유 방지와 같은 확장성에 대한 모범 사례는 여기에 설명되어 있지 않습니다.

RPC 스레딩 모델

서버에서 RPC 호출을 받으면 RPC에서 제공하는 스레드에서 서버 루틴(관리자 루틴)이 호출됩니다. RPC는 워크로드가 변동함에 따라 증가 및 감소하는 적응 스레드 풀을 사용합니다. Windows 2000부터 RPC 스레드 풀의 핵심은 완료 포트입니다. RPC의 완료 포트 및 사용량은 경합 서버 루틴이 0개에서 낮음으로 조정됩니다. 즉, RPC 스레드 풀은 일부 스레드가 차단될 경우 서비스 스레드 수를 적극적으로 증가합니다. 차단은 드물다는 가정하에 작동하며, 스레드가 차단되면 신속하게 해결되는 임시 조건입니다. 이 방법을 사용하면 경합이 적은 서버에 대한 효율성을 높일 수 있습니다. 예를 들어 SAN(고속 시스템 영역 네트워크)을 통해 액세스되는 8개 프로세서 550MHz 서버에서 작동하는 void 호출 RPC 서버는 200개 이상의 원격 클라이언트에서 초당 30,000회 이상의 void 호출을 제공합니다. 이는 시간당 1억 8,000만 개 이상의 호출을 나타냅니다.

결과적으로 서버의 경합이 높을 때 공격적인 스레드 풀이 실제로 방해가 됩니다. 설명하기 위해 원격으로 파일에 액세스하는 데 사용되는 대규모 서버를 상상해 보십시오. 서버가 가장 간단한 접근 방식을 채택한다고 가정합니다. RPC가 서버 루틴을 호출하는 스레드에서 파일을 동기적으로 읽고 씁니다. 또한 많은 클라이언트에 서비스를 제공하는 4개 프로세서 서버가 있다고 가정합니다.

서버는 5개의 스레드로 시작합니다(실제로는 다르지만 간단히 하기 위해 5개의 스레드가 사용됨). RPC가 첫 번째 RPC 호출을 선택하면 서버 루틴에 대한 호출을 디스패치하고 서버 루틴에서 I/O를 실행합니다. 파일 캐시가 누락된 다음 결과를 기다리는 것을 차단하는 경우는 드물게 발생합니다. 차단되는 즉시 다섯 번째 스레드가 해제되어 요청을 수신하고 여섯 번째 스레드가 핫 대기 상태로 만들어집니다. 각 10번째 I/O 작업이 캐시를 놓치고 100밀리초(임의의 시간 값)에 대해 차단한다고 가정하고, 4개 프로세서 서버가 초당 약 20,000개 호출(프로세서당 5,000개 호출)을 처리한다고 가정할 때, 간단한 모델링은 각 프로세서가 약 50개의 스레드를 생성할 것으로 예측합니다. 이렇게 하면 차단할 호출이 2밀리초마다 오고 100밀리초 후에 첫 번째 스레드가 다시 해제되어 풀이 약 200개의 스레드(프로세서당 50개)에서 안정화된다고 가정합니다.

스레드 수가 많을수록 서버 속도가 느려지고 새 스레드 생성 속도가 느려지는 추가 컨텍스트 스위치가 발생하므로 실제 동작은 더 복잡하지만 기본 아이디어는 분명합니다. 서버의 스레드가 I/O 또는 리소스에 대한 액세스와 같은 항목을 차단하고 대기하기 시작하면 스레드 수가 빠르게 늘어나게 됩니다.

들어오는 요청을 게이트하는 RPC 및 완료 포트는 서버에서 사용 가능한 RPC 스레드 수를 컴퓨터의 프로세서 수와 동일하게 유지하려고 합니다. 즉, 4개 프로세서 서버에서 스레드가 RPC로 반환되면 4개 이상의 사용 가능한 RPC 스레드가 있는 경우 다섯 번째 스레드는 새 요청을 선택할 수 없으며, 대신 현재 사용할 수 있는 스레드 중 하나가 차단되는 경우 핫 대기 상태로 유지됩니다. 다섯 번째 스레드가 프로세서 수 아래로 떨어지는 사용 가능한 RPC 스레드 수가 없는 핫 대기 상태로 충분히 오래 대기하는 경우 릴리스됩니다. 즉, 스레드 풀이 감소합니다.

스레드가 많은 서버를 상상해 보십시오. 앞에서 설명한 것처럼 RPC 서버는 스레드가 자주 차단되는 경우에만 많은 스레드로 끝납니다. 스레드가 자주 차단되는 서버에서는 현재 사용할 수 있는 모든 스레드가 차단되고 처리 요청이 제공되므로 RPC로 반환되는 스레드가 곧 핫 대기 목록에서 제외됩니다. 스레드가 차단되면 커널의 스레드 디스패처가 컨텍스트를 다른 스레드로 전환합니다. 이 컨텍스트 전환 자체는 CPU 주기를 사용합니다. 다음 스레드는 다른 코드를 실행하고 다른 데이터 구조에 액세스하며 다른 스택을 갖게 됩니다. 즉, 메모리 캐시 적중률(L1 및 L2 캐시)이 훨씬 낮아 실행 속도가 느려집니다. 동시에 실행되는 수많은 스레드는 힙, 서버 코드의 중요한 섹션 등과 같은 기존 리소스에 대한 경합을 증가합니다. 이렇게 하면 리소스에 대한 호송이 형성됨에 따라 경합이 더욱 증가합니다. 메모리가 낮으면 스레드 수가 많고 증가하는 메모리 압력으로 인해 페이지 오류가 발생하여 스레드가 차단되는 속도가 더 높아지고 더 많은 스레드가 생성됩니다. 차단 빈도 및 사용 가능한 실제 메모리 양에 따라 서버는 높은 컨텍스트 전환 속도로 낮은 수준의 성능에서 안정화되거나 실제 작업을 수행하지 않고 하드 디스크 및 컨텍스트 전환에 반복적으로 액세스하는 지점으로 악화될 수 있습니다. 물론 이 상황은 가벼운 워크로드에서는 표시되지 않지만 워크로드가 많으면 문제가 빠르게 표면화됩니다.

이를 어떻게 방지할 수 있나요? 스레드가 차단되고, 호출을 비동기로 선언하고, 요청이 서버 루틴에 들어가면 I/O 시스템 및/또는 RPC의 비동기 기능을 사용하는 작업자 스레드 풀에 큐에 대기합니다. 서버가 RPC 호출을 수행하는 경우 비동기식으로 만들고 큐가 너무 크지 않은지 확인합니다. 서버 루틴이 파일 I/O를 수행하는 경우 비동기 파일 I/O를 사용하여 I/O 시스템에 여러 요청을 큐에 추가하고 몇 개의 스레드만 큐에 대기하고 결과를 선택합니다. 서버 루틴이 네트워크 I/O를 수행하는 경우 다시 시스템의 비동기 기능을 사용하여 요청을 실행하고 비동기적으로 회신을 선택하고 가능한 한 적은 수의 스레드를 사용합니다. I/O가 완료되거나 서버가 만든 RPC 호출이 완료되면 요청을 전달한 비동기 RPC 호출을 완료합니다. 이렇게 하면 서버가 가능한 한 적은 수의 스레드로 실행될 수 있으므로 서버에서 서비스할 수 있는 클라이언트의 성능과 수가 증가합니다.

규모 확장

NLB가 구성된 경우 지정된 클라이언트 주소의 모든 요청이 동일한 서버로 이동하도록 NLB(네트워크 부하 분산)에서 작동하도록 RPC를 구성할 수 있습니다. 각 RPC 클라이언트는 연결 풀을 열기 때문에(자세한 내용은 RPC 및 네트워크 참조) 지정된 클라이언트 풀의 모든 연결이 동일한 서버 컴퓨터에 있어야 합니다. 이 조건이 충족되는 한 확장성이 뛰어난 하나의 대형 RPC 서버로 작동하도록 NLB 클러스터를 구성할 수 있습니다.