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

アシュリー・スタントン=看護師ブレイディ・ガスタートム・ダイクストラ

この記事では、ASP.NET Core SignalR を使用する高トラフィック アプリのホスティングとスケーリングに関する考慮事項について説明します。

固定セッション

SignalR では、特定の接続に対するすべての HTTP 要求を同じサーバー プロセスで処理する必要があります。 SignalRサーバー ファーム (複数のサーバー) で実行する場合は、"スティッキー セッション" を使用する必要があります。 "スティッキー セッション" は、 セッション アフィニティとも呼ばれます。 Azure App Serviceでは、Microsoft アプリケーション要求ルーティング (ARR) を使用して要求をルーティングします。 App Service アプリで "セッション アフィニティ" (ARR アフィニティ) 設定を有効にすると、スティッキー セッションが有効になります。

アプリにスティッキー セッションが必要ないシナリオは 3 つあります。

  • 1 つのプロセスでの単一サーバーでのホスティング
  • Azure SignalR サービスを使用する (アプリではなく、サービスに対してスティッキー セッションが有効になります)
  • すべてのクライアントが WebSocket のみを使用するように構成され、クライアント構成で SkipNegotiation が有効になります

その他すべてのシナリオ (Redis バックプレーンを使用する場合を含む) では、スティッキー セッション用にサーバー環境を構成する必要があります。

SignalR の Azure App Service の構成に関するガイダンスについては、「ASP.NET Core SignalR アプリを Azure App Service に発行する」を参照してください。 Blazor を使用する SignalR アプリのスティッキー セッションの構成に関するガイダンスについては、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 サービス

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

Azure SignalR Service への接続を確立する様子を示す図。

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

サービスに接続されているクライアントとサーバーを示す図。

スケールアウトに対するこのアプローチには、Redis バックプレーンの代替方法よりもいくつかの利点があります。

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

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

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

Redis バックプレーン

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

1 つのサーバーからすべてのクライアントに送信されたメッセージを含む Redis バックプレーンを示す図。

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

前に説明した Azure SignalR サービスの利点は、Redis バックプレーンの欠点です。

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

Windows クライアント オペレーティング システムに関する IIS の制限事項

Windows 10 および Windows 8.x は、クライアント オペレーティング システムです。 クライアント オペレーティング システムのインターネット インフォメーション サービス (IIS) には、同時接続数が 10 個に制限されています。 SignalR接続には、次の特性があります。

  • これらは一時的で、頻繁に再確立されます。
  • 使用されなくなったとき、 すぐに破棄されることはありません

これらの特性により、クライアント オペレーティング システムで 10 接続の制限に達する可能性があります。 開発にクライアント オペレーティング システムを使用する場合は、次の推奨事項を考慮してください。

  • IIS を回避する
  • 展開ターゲットとして Kestrel または IIS Express を使用する

Nginx を使用した Linux

次のコードには、WebSocket、ServerSentEvents、LongPolling for SignalRを有効にするために必要な最小限の設定が含まれています。

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 オープン ソースでは、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;
       }
    }
    
  • どちらの構成でも、proxy_pass http://localhost:5000 セクションのserverproxy_pass http://backendに変更します。

詳細については、Nginx を使用した Linux Host ASP.NET Coreを参照してください。

その他の SignalR バックプレーン プロバイダー

次の非Microsoft プロバイダーは、SignalR バックプレーンも提供します。