ASP.NET Core SignalR 호스팅 및 크기 조정

작성자: Andrew Stanton-Nurse, Brady GasterTom Dykstra

이 문서에서는 ASP.NET Core SignalR을 사용하는 트래픽이 많은 앱의 호스팅 및 크기 조정 고려 사항에 대해 설명합니다.

고정 세션

SignalR에서는 특정 연결에 대한 모든 HTTP 요청을 동일한 서버 프로세스에서 처리해야 합니다. SignalR이 서버 팜(여러 서버)에서 실행되는 경우 "고정 세션"을 사용해야 합니다. "고정 세션"은 일부 부하 분산 장치에서 세션 선호도라고도 합니다. Azure App Service는 ARR(애플리케이션 요청 라우팅)을 사용하여 요청을 라우팅합니다. Azure App Service에서 "ARR 선호도" 설정을 사용하도록 설정하면 "고정 세션"이 활성화됩니다. 고정 세션이 필요하지 않은 유일한 상황은 다음과 같습니다.

  1. 단일 서버에서 호스팅하는 경우 단일 프로세스로 수행합니다.
  2. Azure SignalR Service를 사용하는 경우
  3. 모든 클라이언트가 WebSocket 사용하도록 구성되고 클라이언트 구성에서 SkipNegotiation 설정이 활성화된 경우

Redis 백플레인을 사용하는 경우를 포함하여 다른 모든 상황에서는 고정 세션에 대해 서버 환경을 구성해야 합니다.

Azure App Service 구성하는 방법에 대한 SignalR지침은 Azure App Service ASP.NET Core SignalR 앱 게시를 참조하세요. Azure SignalR 서비스를 사용하는 앱에 대한 고정 세션을 구성하는 방법에 대한 Blazor 지침은 ASP.NET Core Blazor Server호스트 및 배포를 참조하세요.

TCP 연결 리소스

웹 서버에서 지원할 수 있는 동시 TCP 연결 수는 제한되어 있습니다. 표준 HTTP 클라이언트는 임시 연결을 사용합니다. 이러한 연결은 클라이언트가 유휴 상태가 되면 닫혔다가 나중에 다시 열 수 있습니다. 반면에 SignalR 연결은 영구입니다. SignalR 연결은 클라이언트가 유휴 상태인 경우에도 열린 상태로 유지됩니다. 많은 클라이언트를 제공하는 트래픽이 많은 앱에서 이러한 영구 연결로 인해 서버가 최대 연결 수에 도달할 수 있습니다.

또한 영구 연결은 각 연결을 추적하기 위해 일부 추가 메모리를 소비합니다.

SignalR에서 연결 관련 리소스를 많이 사용하면 동일한 서버에서 호스트되는 다른 웹앱에 영향을 줄 수 있습니다. SignalR가 사용 가능한 마지막 TCP 연결을 열고 유지하면 동일한 서버의 다른 웹앱에도 사용할 수 있는 연결이 더 이상 없습니다.

서버에 연결이 끊어지면 임의의 소켓 오류 및 연결 다시 설정 오류가 표시됩니다. 예를 들어:

An attempt was made to access a socket in a way forbidden by its access permissions...

SignalR 리소스 사용으로 인해 다른 웹앱에서 오류가 발생하는 것을 방지하려면 다른 웹앱과 다른 서버에서 SignalR을 실행합니다.

SignalR 리소스 사용으로 인해 SignalR 앱에서 오류가 발생하는 것을 방지하려면 서버에서 처리해야 하는 연결 수를 제한하도록 확장합니다.

확장

SignalR을 사용하는 앱은 모든 연결을 추적해야 하므로 서버 팜에 문제가 발생합니다. 서버를 추가하면 다른 서버에서 알 수 없는 새 연결을 가져옵니다. 예를 들어 다음 다이어그램의 각 서버에 있는 SignalR은 다른 서버의 연결을 인식하지 못합니다. 서버 중 하나의 SignalR이 모든 클라이언트에 메시지를 보내려는 경우 메시지는 해당 서버에 연결된 클라이언트에게만 전달됩니다.

백플레인 없이 크기 조정 SignalR

이 문제를 해결하기 위한 옵션은 Azure SignalR ServiceRedis 백플레인입니다.

Azure SignalR 서비스

Azure SignalR Service는 실시간 트래픽에 대한 프록시 역할을 하며 앱이 여러 서버에 걸쳐 확장되면 백플레인으로 두 배로 작동합니다. 클라이언트가 서버에 대한 연결을 시작할 때마다 클라이언트는 서비스에 연결하도록 리디렉션됩니다. 이 프로세스는 다음 다이어그램에 설명되어 있습니다.

Azure SignalR 서비스에 대한 연결 설정

그 결과 서비스는 모든 클라이언트 연결을 관리하지만 각 서버는 다음 다이어그램에 표시된 것처럼 서비스에 대한 일정한 수의 연결만 필요합니다.

서비스에 연결된 클라이언트, 서비스에 연결된 서버

규모 확장에 대한 이 방식은 Redis 백플레인 대안에 비해 몇 가지 장점이 있습니다.

  • 클라이언트 선호도라고도 하는 고정 세션은 클라이언트가 연결될 때 Azure SignalR Service로 즉시 리디렉션되기 때문에 필요하지 않습니다.
  • SignalR 앱은 전송된 메시지 수에 따라 스케일 아웃할 수 있는 반면, Azure SignalR Service는 연결 수에 관계없이 처리하도록 확장할 수 있습니다. 예를 들어 수천 개의 클라이언트가 있을 수 있지만 초당 몇 개의 메시지만 전송되는 경우 SignalR 앱은 연결을 직접 처리하기 위해 여러 서버로 스케일 아웃할 필요가 없습니다.
  • SignalR 앱은 SignalR이 없는 웹앱보다 훨씬 더 많은 연결 리소스를 사용하지 않습니다.

이러한 이유로 App Service, VM 및 컨테이너를 포함하여 Azure에서 호스트되는 모든 ASP.NET Core SignalR 앱에 대해 Azure SignalR Service를 사용하는 것이 좋습니다.

자세한 내용은 Azure SignalR Service 설명서를 참조하세요.

Redis 백플레인

Redis는 게시/구독 모델을 사용하여 메시징 시스템을 지원하는 메모리 내 키-값 저장소입니다. SignalR Redis 백플레인은 pub/sub 기능을 사용하여 메시지를 다른 서버로 전달합니다. 클라이언트가 연결되면 연결 정보가 백플레인에 전달됩니다. 서버는 모든 클라이언트에 메시지를 보내려고 할 때 백플레인으로 보냅니다. 백플레인은 연결된 모든 클라이언트와 클라이언트가 있는 서버를 알고 있습니다. 해당 서버를 통해 모든 클라이언트에 메시지를 보냅니다. 이 프로세스는 다음 다이어그램에 설명되어 있습니다.

Redis 백플레인, 한 서버에서 모든 클라이언트로 보낸 메시지

Redis 백플레인은 자체 인프라에서 호스트되는 앱에 권장되는 스케일 아웃 접근 방식입니다. 데이터 센터와 Azure 데이터 센터 간에 상당한 연결 대기 시간이 있는 경우 Azure SignalR Service는 대기 시간이 짧거나 처리량 요구 사항이 높은 온-프레미스 앱에 적합한 옵션이 아닐 수 있습니다.

앞서 언급한 Azure SignalR Service 장점은 Redis 백플레인의 단점입니다.

  • 다음 두 가지가 모두 true인 경우를 제외하고 클라이언트 선호도라고도 하는 고정 세션이 필요합니다.
    • 모든 클라이언트는 WebSocket 사용하도록 구성됩니다.
    • SkipNegotiation 설정은 클라이언트 구성에서 사용하도록 설정됩니다. 서버에서 연결이 시작되면 해당 서버에 연결이 유지됩니다.
  • 메시지가 거의 전송되지 않더라도 클라이언트 수에 따라 SignalR 앱을 스케일 아웃해야 합니다.
  • SignalR 앱은 SignalR이 없는 웹앱보다 훨씬 더 많은 연결 리소스를 사용합니다.

Windows 클라이언트 OS에 대한 IIS 제한 사항

Windows 10 및 Windows 8.x는 클라이언트 운영 체제입니다. 클라이언트 운영 체제의 IIS에는 10개의 동시 연결 제한이 있습니다. SignalR의 연결은 다음과 같습니다.

  • 일시적이고 자주 다시 설정됩니다.
  • 더 이상 사용되지 않는 경우에도 즉시 삭제되지 않습니다.

위의 조건으로 인해 클라이언트 OS에서 10개의 연결 제한에 도달할 수 있습니다. 개발에 클라이언트 OS를 사용하는 경우 다음을 권장합니다.

  • IIS를 사용하지 않습니다.
  • Kestrel 또는 IIS Express를 배포 대상으로 사용합니다.

Nginx를 사용하는 Linux

다음은 SignalR에 대해 WebSocket, ServerSentEvents 및 LongPolling을 사용하도록 설정하는 데 필요한 최소 설정을 포함합니다.

http {
  map $http_connection $connection_upgrade {
    "~*Upgrade" $http_connection;
    default keep-alive;
  }

  server {
    listen 80;
    server_name example.com *.example.com;

    # Configure the SignalR Endpoint
    location /hubroute {
      # App server url
      proxy_pass http://localhost:5000;

      # Configuration for WebSockets
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection $connection_upgrade;
      proxy_cache off;
      # WebSockets were implemented after http/1.0
      proxy_http_version 1.1;

      # Configuration for ServerSentEvents
      proxy_buffering off;

      # Configuration for LongPolling or if your KeepAliveInterval is longer than 60 seconds
      proxy_read_timeout 100s;

      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
    }
  }
}

여러 백 엔드 서버를 사용하는 경우 연결 시 SignalR 연결이 서버를 전환하지 못하도록 고정 세션을 추가해야 합니다. Nginx에서 고정 세션을 추가하는 방법에는 여러 가지가 있습니다. 사용 가능한 항목에 따라 두 가지 방식이 아래에 표시됩니다.

이전 구성 외에도 다음이 추가되었습니다. 다음 예제에서 backend는 서버 그룹의 이름입니다.

Nginx Open Source를 통해 ip_hash를 사용하여 클라이언트의 IP 주소에 따라 서버로 연결을 라우팅합니다.

http {
  upstream backend {
    # App server 1
    server localhost:5000;
    # App server 2
    server localhost:5002;

    ip_hash;
  }
}

Nginx Plus를 통해 sticky를 사용하여 요청에 cookie를 추가하고 사용자의 요청을 서버에 고정합니다.

http {
  upstream backend {
    # App server 1
    server localhost:5000;
    # App server 2
    server localhost:5002;

    sticky cookie srv_id expires=max domain=.example.com path=/ httponly;
  }
}

마지막으로 server 섹션의 proxy_pass http://localhost:5000proxy_pass http://backend로 변경합니다.

Nginx를 통한 WebSocket에 대한 자세한 내용은 WebSocket 프록시로서의 NGINX를 참조하세요.

부하 분산 및 고정 세션에 대한 자세한 내용은 NGINX 부하 분산을 참조하세요.

Nginx를 사용하는 ASP.NET Core에 대한 자세한 내용은 다음 문서를 참조하세요.

타사 SignalR 백플레인 공급자

다음 단계

자세한 내용은 다음 자료를 참조하세요.