ASP.NET Core SignalR のホスティングとスケーリング

作成者: Andrew Stanton-NurseBrady 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 バックプレーンが使用されている場合を含む) では、サーバー環境を固定セッション用に構成する必要があります。

SignalR の Azure App Service の構成に関するガイダンスについては、「ASP.NET Core SignalR アプリを Azure App Service に発行する」を参照してください。 Azure SignalRService を使用する Blazor アプリのスティッキー セッションの構成に関するガイダンスについては、「ASP.NET Core サーバー側 Blazor アプリホストしてデプロイする」を参照してください。

TCP 接続リソース

Web サーバーでサポートできる同時 TCP 接続の数は制限されています。 標準 HTTP クライアントでは、"エフェメラル" 接続が使用されます。 これらの接続は、クライアントがアイドル状態になったときに閉じて、後で再度開くことができます。 一方、SignalR 接続は、"固定" です。 SignalR 接続は、クライアントがアイドル状態になっても開いたままです。 多くのクライアントにサービスを提供する高トラフィック アプリでは、これらの固定接続により、サーバーが最大接続数に達する可能性があります。

固定接続は、各接続を追跡するために、追加のメモリも消費します。

SignalR による接続関連のリソースの大量使用は、同じサーバーでホストされている他の Web アプリに影響を与える可能性があります。 SignalR が開いており、最後の使用可能な TCP 接続が保持されている場合、同じサーバー上の他の Web アプリでは、さらに多くの接続を使用することもできなくなります。

サーバーの接続が不足すると、ランダムなソケット エラーと接続リセット エラーが表示されます。 次に例を示します。

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

他の Web アプリで SignalR リソースの使用によってエラーが発生しないようにするには、他の Web アプリとは異なるサーバーで SignalR を実行します。

SignalR アプリで SignalRリソースの使用によってエラーが発生しないようにするには、スケールアウトしてサーバーが処理する必要のある接続の数を制限します。

スケール アウト

SignalR を使用するアプリでは、すべての接続を追跡する必要があるため、サーバー ファームに関する問題が発生します。 サーバーを追加すると、他のサーバーが認識していない新しい接続が取得されます。 たとえば、次の図の各サーバー上の SignalR では、他のサーバー上の接続が認識されません。 いずれかのサーバー上の SignalR ですべてのクライアントにメッセージを送信する必要がある場合、メッセージはそのサーバーに接続されているクライアントにのみ送信されます。

バックプレーンなしでの SignalR のスケーリング

この問題を解決するためのオプションは、Azure SignalR ServiceRedis バックプレーン です。

Azure SignalR Service

Azure SignalR Service は、リアルタイム トラフィック用のプロキシとして機能し、アプリが複数のサーバーにわたってスケールアウトされると、バックプレーンとしても機能します。 クライアントがサーバーへの接続を開始するたびに、クライアントはリダイレクトされ、サービスに接続されます。 このプロセスを次の図に示します。

Azure SignalR Service に接続を確立中

その結果、次の図に示すように、サービスではすべてのクライアント接続が管理されますが、各サーバーに必要なサービスへの接続は、少数で一定の数のみになります。

サービスに接続されたクライアント、サービスに接続されたサーバー

このスケールアウトのアプローチは、Redis バックプレーンの代替案と比べていくつかの利点があります。

  • クライアントは接続されるとただちに Azure SignalR Service にリダイレクトされるため、固定セッション (クライアント アフィニティとも呼ばれます) は必要ありません。
  • SignalR アプリでは、送信されるメッセージの数に基づいてスケールアウトできますが、Azure SignalR Service では、任意の数の接続を処理できるようにスケーリングします。 たとえば、数千のクライアントが存在する可能性はあっても、1 秒あたり数個のメッセージしか送信されない場合、SignalR アプリでは、接続自体を処理するためだけに複数のサーバーにスケールアウトする必要はありません。
  • SignalR アプリで使用される接続リソースの数は、SignalR を使用しない Web アプリと比べて大幅に増えることはありません。

これらの理由から、App Service、VM、コンテナーなど、Azure でホストされているすべての ASP.NET Core SignalR アプリには、Azure SignalR Service を使用することをお勧めします。

詳細については、Azure SignalR Service のドキュメントを参照してください。

Redis バックプレーン

Redis は、パブリッシュ/サブスクライブ モデルを使用するメッセージング システムをサポートする、キーと値のメモリ内ストアです。 SignalR Redis バックプレーンでは、パブリッシュ/サブスクライブ機能を使用して、メッセージを他のサーバーに転送します。 クライアントで接続が確立されると、接続情報がバックプレーンに渡されます。 1 台のサーバーからすべてのクライアントにメッセージを送信する場合、メッセージはバックプレーンに送信されます。 バックプレーンでは、接続されているすべてのクライアントと、それらが配置されているサーバーが認識されています。 メッセージは、それぞれのサーバーを介してすべてのクライアントに送信されます。 このプロセスを次の図に示します。

Redis バックプレーン、1 台のサーバーからすべてのクライアントに送信されるメッセージ

Redis バックプレーンは、お使いのインフラストラクチャでホストされているアプリに対して推奨されるスケールアウト アプローチです。 データ センターと Azure データ センターの間に大きな接続の待機時間がある場合、Azure SignalR Service は、低待機時間または高スループットの要件を持つオンプレミス アプリ用としては実用的なオプションではない可能性があります。

前述した Azure SignalR Service の利点は、Redis バックプレーンでは欠点になります。

  • 次の条件の両方が真である場合を除いて、固定セッション (クライアント アフィニティとも呼ばれます) が必要です。
    • すべてのクライアントが、WebSockets のみを使用するように構成されている。
    • クライアント構成で、SkipNegotiation 設定が有効になっている。 サーバーで接続が開始されると、接続はそのサーバー上で維持される必要があります。
  • SignalR アプリでは、送信されるメッセージが少ない場合でも、クライアントの数に基づいてスケールアウトする必要があります。
  • SignalR アプリでは、SignalRを使用しない Web アプリよりもはるかに多くの接続リソースが使用されます。

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 で固定セッションを追加するには複数の方法があります。 使用可能なものによって異なる 2 つのアプローチを次に示します。

前述の構成に加えて、次のものを追加します。 次の例で、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 バックプレーン プロバイダー

次のステップ

詳細については、次のリソースを参照してください。