Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
連載:コードから触るIIS 8

連載:コードから触るIIS 8

RedisをBackplaneとしたSignalRのスケールアウト

2014年1月28日

SignalRアプリをスケールアウトする際の注意点と、それを回避するためのBackplane機構について説明。さらにRedisをBackplaneとして活用する方法を解説する。

株式会社グラニ 田中 孝佳
  • このエントリーをはてなブックマークに追加

 前々回の記事ではIIS 8でWebSocketを有効にしてSignalRのアプリケーションを動かす方法を紹介した。今回はSignalRアプリケーションにおけるスケールアウトについて紹介したい。本稿のサンプルは、こちらからダウンロードできる。

SignalRのスケールアウトとBackplane

 SignalRでも通常のWebアプリケーションと同様、複数のWebサーバーを立てることにより、1台では処理できない量のリクエストを処理することが可能になる。ただし、SignalR、あるいはWebSocket一般において注意すべきことがある。

SignalRスケールアウト時の注意点

 ユーザーからのリクエストは、例えば次の図のようにロードバランサーによって複数のWebサーバーに振り分けされる。そのため、ユーザーAがアクセスするWebサーバーと、ユーザーBがアクセスするWebサーバーは異なる(場合がある)。SignalRの場合、サーバーから全クライアントに送信したり、クライアントから別のクライアントに送信したりするユースケースが考えられる。ところが、このままだとサーバーAはサーバーBに接続しているユーザーBに対してレスポンスを返すことができないため、これらのユースケースで利用できなくなってしまう。

複数台のWebサーバーでスケールアウトすると、異なるユーザーは別のサーバーに接続する可能性がある。
複数台のWebサーバーでスケールアウトすると、異なるユーザーは別のサーバーに接続する可能性がある

SignalRのBackplane機構

 そこでSignalRは、次の図のような「Backplane」と呼ばれる機構をサポートしている。例えばユーザーからイベントを受けて、接続している全てのクライアントにレスポンスを送信するユースケースを考えてみよう。サーバーAがユーザーからのリクエストで受けたイベントは、いったんBackplaneに送信される。Backplaneは接続している全てのサーバーに対してこのイベントを配信する。配信されたイベントを受けたサーバーAとBは、接続しているクライアントに対して送信する。これにより、異なるサーバーに接続していても、全てのユーザーに対してレスポンスを送れることになる。

Backplaneにより、SignalRは異なるサーバーに接続しているユーザーに対してもイベントをやりとりすることができる
Backplaneにより、SignalRは異なるサーバーに接続しているユーザーに対してもイベントをやりとりすることができる

 SignalRではBackplaneとして、

  • Microsoft Azure(旧称:Windows Azure) Service Bus
  • Redis
  • SQL Server(Service Brokerを利用)

の3つが利用可能である。

 そこで今回は、前々回のサンプルアプリに対して、RedisをBackplaneとして利用してみよう。Redisに関しては、「C#のRedisライブラリ「BookSleeve」の利用法」などを参考にしてほしい。

 実際にアプリに手を加える前に、Backplaneの実装内容について少し説明したい。SignalRのソースはGitHubで公開されており、これを参考にする。

Backplaneの実装内容について

 まずSignalRは、受け取ったメッセージを全てMessage Busに送信する。Message Busは、IMessageBusインターフェースを実装し、メッセージのPublish(発行)とSubscribe(購読)のメソッドを持っている。

 RedisをBackplaneとするためのライブラリを導入すると、このIMessageBusのデフォルトの実装をRedisMessageBusクラスに置き換える。SignalRのメッセージのPublishとSubscribeはRedisのPub/Sub機構を利用している。また、この実装を見ると、Redisへの接続ライブラリにはBookSleeveが使われているのが分かる。

 また、Backplaneでどのクライアントにどこまでメッセージを送ったか(「Cursor」と呼んでいる)を管理しているため、クライアントがサーバーから切断し、再接続したときに別のサーバーに接続したとしても前回の続きの情報が送信される。

 Backplaneを利用すると、このように複数サーバーでのスケールアウトが可能になるが、パフォーマンス上の制約がある。これはSignalRの利用用途に依存しており、例えば株価情報などのサーバーからのリアルタイム配信の場合はBackplaneの利用が非常に適している。チャットのようなクライアントからのリクエストを他のクライアントに送信するアプリケーションは、接続するクライアントが増えると、やりとりするメッセージの量が急激に増加するため、Backplaneがボトルネックになりやすい。さらに、ゲームのようなクライアントとサーバー間でリアルタイムにやりとりするようなアプリケーションにはBackplaneは向かない。今回は、前々回のサンプルにBackplaneを追加する方法を紹介するために利用するが、このサンプルはBackplaneを利用してスケールアウトするには不向きであることに留意してほしい。

RedisをBackplaneとして利用する

 さて、それではRedisをBackplaneとして利用するようにアプリを書き換えるが、まずRedisサーバーを用意したい。

Redisサーバーの準備

 Windows上にRedisサーバーを用意することもできるが、Windows上でのRedisはまだ直接サポートされているわけではなく、Microsoft Open Tech groupが実験的に提供している。最新の安定版である2.8もまだWindows版では提供されていない。そこで今回は、Azure 仮想マシン(以下、「Azure」と表記)上に「Ubuntu 12.04 LTS」のインスタンスを立てて、Redisをインストールした。Redisのインストールは原則公式サイトの手順に従えばよいが、今回使用した環境での手順は下記の通りである。

Bash
sudo apt-get update
sudo apt-get -yV upgrade
sudo apt-get install make build-essential
 
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
 
cd src/
sudo cp redis-server /usr/local/bin/
sudo cp redis-cli /usr/local/bin/
 
redis-server
Azure上のUbuntu 12.04 LTSにRedisをインストールするためのコマンド(SSHで接続)

 最後のredis-serverのコマンドでRedisサーバーが起動する。

Redisサーバーのポート公開

 このRedisに外部から接続する場合、ファイヤーウォールの設定などでデフォルト6379番ポートを公開する必要がある。特にAzureの場合は、エンドポイントでパブリックポートとプライベートの6379番ポートを設定する必要がある(次の画面はその例)。

Azureのダッシュボードで、Redisのデフォルトポート6379をパブリック側で9826番ポートとして公開している

 これでRedisサーバーの準備はできた。

Backplaneの追加

 いよいよアプリにBackplaneを追加するが、作業としては非常に簡単である。

 まず、今回利用する前々回のサンプルをダウンロードし、Visual Studioでソリューションを開く。

 次に、Microsoft.AspNet.SignalRライブラリをNuGetからインストールする。これには、パッケージマネージャーコンソールを開き、次のように入力する。

パッケージマネージャーコンソール
Install-Package Microsoft.AspNet.SignalR.Redis
NuGetからMicrosoft.AspNet.SignalR.Redisライブラリをインストールする

 次に、スタートアップクラスを編集する。今回のサンプルでは、Startup1.csファイルである。

C#(Startup1.cs)
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
 
[assembly: OwinStartup(typeof(SignalR.MoveShapeDemo.Startup1))]
namespace SignalR.MoveShapeDemo
{
  public class Startup1
  {
    public void Configuration(IAppBuilder app)
    {
      // この1行を追加。第1引数: サーバー名はAzureで指定したDNS。
      // 第2引数: ポートはAzure上でPublicに公開したものを指定。
      // 第3引数: Redisのデフォルト設定ではパスワード無し。
      // 第4引数: RedisのPub/Subチャンネル名となる任意のアプリケーションキー。
      GlobalHost.DependencyResolver.UseRedis("ubuntu-redis.cloudapp.net", 9826, "", "BuildInsider7");
      // SignalRの初期化。/signalrへのリクエストをSignalRで処理するようにする。
      app.MapSignalR();
    }
  }
}
BackPlaneにRedisを利用する処理を追加している(サーバー名、ポートは各自の環境に合わせてほしい)

 これで完了である。このまま起動すればローカルのIISからAzure上のRedisをBackplaneとして利用することで起動する。

Backplaneの追加

 今回はスケールアップの確認をするために、Azure上に2つのWindows Server 2012を起動し、それぞれにこのアプリを起動させてみた(次の画面)。実際のアプリとしてスケールアウトするときには、これら複数のサーバーをロードバランサー配下に配置し、同じDNSアドレスでアクセスするのが一般的だが、今回は分かりやすさのためにそれぞれのサーバーのDNSアドレスで接続している。

異なるWebサーバー(=WebブラウザーのURLアドレス名を参照)に起動した同じアプリを動かしている様子

RedisをBackplaneとして使用しているため、片方でオブジェクトを動かすと、別のサーバーに接続しているもう片方のブラウザーでもオブジェクトが追随する。

 また、Redisクライアントのコマンドラインコンソールを使うと、次の画面に示すように、Redisでやりとりされているメッセージを見ることができる。

Redisサーバー上で、redis-cliコマンドで「SUBSCRIBE BuildInsider7」(=Startup1.csファイルで追加したメソッドの第4引数に指定したアプリケーションキーを指定)を実行して、Redisがやりとりしているメッセージを表示している

クライアントから直接、SignalRにつなぐ

 サンプルアプリでは、WebページのJavaScriptコードからSignalRで接続していた。しかし、テストや負荷をかけるなどの目的でクライアントから直接、SignalRに接続したいこともあるだろう。そのためのコードを紹介する。このコードも「SignalRClient」というフォルダーで最新のサンプルコードに含んでいる。

 プロジェクトを作成する場合は、Visual Studioで「コンソールアプリケーション」を作成し、パッケージマネージャーコンソールを開いて、次のコマンドによりMicrosoft.AspNet.SignalR.Clientライブラリをインストールする。

パッケージマネージャーコンソール
Install-Package Microsoft.AspNet.SignalR.Client
NuGetからMicrosoft.AspNet.SignalR.Clientライブラリをインストールする

 続いて、以下のようなコードを記述する。

C#(Program.cs)
using System;
using Microsoft.AspNet.SignalR.Client;
using Newtonsoft.Json;
 
namespace SignalRClient
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      // サーバー名を指定
      using (var conn = new HubConnection("http://signalr-i1.cloudapp.net/"))
      {
        // サーバーで作成したHubの名前を指定
        var proxy = conn.CreateHubProxy("moveShapeHub");
        // 受信する
        proxy.On<ShapeModel>("updateShape", m => Console.WriteLine(m.Left + ", " + m.Top));
        conn.Start().Wait();
        // Transportを指定することも可能
        //conn.Start(new LongPollingTransport()).Wait();
 
        // 送信する
        var random = new Random();
        proxy.Invoke("updateModel", new ShapeModel()
        {
          Top = random.NextDouble()<span class="da-footnote-ref">*10</span>0,
          Left = random.NextDouble()<span class="da-footnote-ref">*40</span>0
        }).Wait();
 
        Console.ReadKey();
 
        // 終了
        conn.Stop();
      }
    }
  }
 
  /// <summary>
  /// サーバー側で定義しているのと同じ、やりとりするJSONデータの定義クラス。
  /// </summary>
  public class ShapeModel
  {
    [JsonProperty("left")]
    public double Left { get; set; }
 
    [JsonProperty("top")]
    public double Top { get; set; }
 
    [JsonIgnore]
    public string LastUpdatedBy { get; set; }
  }
}
コンソールからSignalRで接続するためのコード

 これで、他のブラウザーからオブジェクトを操作すると、コンソールに座標が表示されるはずだ。また、コメントにある通り、Transportを指定することもできる。

実運用のために考慮すべきこと

 このように、RedisをBackplaneとして利用すること自体は、ライブラリを追加し、設定を1行追加するだけであり、非常に簡単に利用できる。しかし、実際に運用するためにはいくつか考慮しておかないといけない点がある。

 まず、このままだとRedisが単一サーバーで稼働しているため、何か障害が起きるとSignalRの機能が使えなくなってしまう。これについてはRedisをMaster/Slave構成にしてMasterで障害が起きたらSlaveを昇格させる方法や、Redis Clusterを組む方法などが考えられる。この点を考慮すると、Azure上で構築する場合、RedisではなくAzure Service BusをBackplaneに採用する案も考えられる。一方、AWS上で構築する場合は、マネージドなRedisサービスでマスター/スレーブレプリケーション機能を提供している、AWS ElastiCacheのRedis版を利用することができる(※2014/01/28 追記)。

 また、アプリケーションとIISについても考慮すべきことがあり、公式サイトでも「SignalR Performance」として紹介されている。例えばアプリケーション側では、やりとりするデータ量を減らすために、JSONのキー文字列の長さを短くする方法などが紹介されている。またIIS側では、同時に並行処理可能なリクエスト数の上限を上げる設定などが紹介されている。さらに、SignalR関連のパフォーマンスカウンターを設定する方法も書かれている。

 今回はSignalRのスケールアウトということでRedisをBackplaneとして利用する方法を紹介した。

 次回以降は、IISのスケールアウトに関して紹介したい。まず次回はワーカープロセスを複数立ち上げるWebガーデンについて紹介する。

※以下では、本稿の前後を合わせて5回分(第5回~第9回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

連載:コードから触るIIS 8
5. IIS 8でさわるSignalR 2.0

SignalR 2.0をIIS 8(WebSocket)で動かす方法を説明。Windows Server/IIS、.NET/Visual Studioに関するアップデートについても簡単に紹介。

連載:コードから触るIIS 8
6. Windows Server 2012 R2とIIS 8.5の新機能

先日(11月1日)から製品販売が開始されている「Windows Server 2012 R2」の新機能の中から「PowerShell DSC」について紹介。また、そのOS上で動くIISの最新版「IIS 8.5」の新機能も説明する。

連載:コードから触るIIS 8
7. 【現在、表示中】≫ RedisをBackplaneとしたSignalRのスケールアウト

SignalRアプリをスケールアウトする際の注意点と、それを回避するためのBackplane機構について説明。さらにRedisをBackplaneとして活用する方法を解説する。

連載:コードから触るIIS 8
8. Webガーデンによるアプリケーションプールのマルチプロセス化

ASP.NET アプリのスケーリング方法を解説。今回は、Webサイトを1つのアプリケーションプール上の複数のワーカープロセスで動かす「Webガーデン」について説明する。

連載:コードから触るIIS 8
9. Webファームによる負荷分散(1): Webファームの基本構造と構成

ASP.NETアプリをスケーリングする方法の1つとして、複数のサーバーによる水平負荷分散を実現する「Webガーデン」というIIS機能について説明する。

サイトからのお知らせ

Twitterでつぶやこう!