SignalR の Scale Out とロード バランサー対応 (SignalR.WindowsAzureServiceBus 編)

環境 :
Visual Studio 2010
Windows Azure SDK for .NET 1.6
SignalR 0.5
SignalR.WindowsAzureServiceBus 0.1

こんにちは。

「Community Open Day 2012 in Hokuriku」にご参加いただいた皆様、ありがとうございました。50 分って、ほんと 短いですね。予定していた内容の半分くらいしか話せませんでした。

以下に、セッションで説明が不充分になってしまった箇所について、補足しておきます。

  • 今回 説明しなかったサーバー サイドにおける非同期の話については、こちら (ASP.NET MVC で async を乗りこなす) に記載しています。(セッションでは説明しませんでしたが、非常に重要な話なので、鈴木さんの説明とあわせ、是非、細かく理解しておいてください。)
    なお、ここで書かれている動作は、ASP.NET MVC、ASP.NET Web API、WCF などの「フレームワーク」がおこなっています。つまり、言語の非同期実装 自体がこうした動作をおこなっているわけではないので、サーバー サイドでこのように書けば、常にノンブロッキングでスレッドが使用されるわけではありません。(非同期ランタイムや言語実装そのものの話については、是非、鈴木さんのブログを参照してみてください。)
  • 紹介させて頂いた Windows Developer Days のセッション動画 (ビデオ) は、こちら (Channel 9 – Windows Developer Days 2012) で確認できます。(基本的な話は、こちらでご確認ください。)
    なお、開発環境について更新があるので補足させてください。IIS Express を使用した WebSocket 開発ですが、IIS 8 Express の 4 月の更新により、IIS Express を使った WebSocket 開発が可能になりました。さらに、先日リリースされた Visual Studio 2012 RC では、最新の IIS 8 Express が入っているため、Visual Studio 2012 (IIS Express) を使って WebSocket サーバーの開発とデバッグが可能になっています。(環境面での最新情報も話をさせていただく予定でしたが、説明する時間がありませんでした。残念無念)
  • デモで紹介した RavenDB については、こちら (RavenDB の特徴と使い方) に日本語で解説しているので本ブログでの紹介は省略させて頂きます。また、IoC については、以前、こちら (ASP.NET Web API – IoC と関心事の分離) で紹介しています。(これらを組み合わせたのが、先日紹介したコードになります。)
  • デモで 1 箇所失敗してしまった ASP.NET Web API の Query についてですが、調べてみたところ、[Queryable] 属性を追加する必要があったようです。(すみません、RC 版から変更されていました。) 「Visual Studio 2012 RC リリースに伴う Beta からの変更」 に追記しておきました。

この他に、まだまだ説明できなかった内容が山盛りありますが、残りは、また別の機会などに説明したいと思います。

さて、今回 (以降) は、デモでお見せした SignalR の Scale Out について、設定やソース コードを簡単に見せて終わってしまったので、実際の動かし方 (手順) を以下に記載しておきます。

 

WebSocket における Scale Out (Scaling) の考慮点

リアルタイム Web における Scale Out を考慮する場合、3 階層アプリケーションなどで一般的な「データの共有」ではなく、「プロセス間の連携」が必要です。例えば、WebSocket では、各ノード (負荷分散されたノード) でクライアントと tcp 接続をおこなっているため、チャットのアプリケーションなどで、参加者全員に通知をおこなう場合などには、この「接続」を共有する必要があります。

こうしたプロセス間連携の実装手段としては、さまざまな方式が考えられます。プリミティブな手段としては、WCF の UDP 通信 を使ったブロード キャストもこうした手段の 1 つでしょう。(なお、Windows Azure にホストする場合は、UDP は使えないので注意してください。) 応用的な手法を考える場合、可用性 (例えば、単一点障害の防止など)、一貫性 (例えば、long polling などで発生する Lost の回避や、順序保障) などの品質要件に応じて、設計やアーキテクチャの選択肢 (合意プロトコル方式、共有データ方式、など) が変わってきます。このため、ご紹介したように、ASP.NET で WebSocket の Scaling に対処する場合は、こうした要件や設計上の観点を考慮して、適切なコンポーネントの採用や、実装が必要となります。 

さて、SignalR では、こうしたクライアント間の連携部分が SignalR に隠ぺいされていますが、セッションで紹介したように、こうした Scaling への対処方法として、最近、Windows Azure ServiceBus の topic や Redis (の Pub/Sub の機能) と連携可能なパッケージがリリースされました。(下記は、2012 年 06 月時点の SignalR 関連のパッケージの一覧です。太字の SignalR.Redis、SignalR.WindowsAzureServiceBus は、2012 年 05 月にバージョン 0.1 がリリースされました。)
これらは、Publisher / Sunscriber 型の連携アーキテクチャに基づく Scaling のためのモジュールです。例えば、複数のインスタンスにサーバーが (Farm として) 分散して配置されている場合、これらの個々のサーバーは 「購読者」(Subscriber) として連携モジュール (Azure の topic、Redis、など) に登録し、これらサーバーのうちの 1 つが通知 (Publish) をおこなうと、購読者 (Subscriber) である すべてのサーバーに通知されます。

PM> get-package -filter signalr -listavailable

Id                             Version                              
--                             -------                              
FleetsMapTrackingWithSignalR   0.9.3
Gate.Quickstart.SignalR        0.3.6
jquery.captureDocumentWrite    1.0
MagicalUnicornMvcErrorToolkit  1.0
Pushqa.Client                  0.1.1
Pushqa.Server                  0.1.1
SheepJax                       1.2.3.1
SignalR                        0.5.0
SignalR.Client                 0.5.0
SignalR.Client.Silverlight     0.5.0
SignalR.Client.Silverlight5    0.5.0
SignalR.Client.WP7             0.5.0
SignalR.EventStream            1.0.0.5
SignalR.Hosting.AspNet         0.5.0
SignalR.Hosting.Common         0.5.0
SignalR.Hosting.Owin           0.5.0
SignalR.Hosting.Self           0.5.0
SignalR.Js                     0.5.0
SignalR.Ninject                0.5.0
SignalR.RabbitMq               0.5.0  *
SignalR.Reactive               0.5.1
SignalR.Redis                  0.1.0  *
SignalR.Sample                 0.3.1
SignalR.Server                 0.5.0
SignalR.StructureMap           0.4.1
SignalR.WebSockets             0.3.3
SignalR.WindowsAzureServiceBus 0.1.0  *
WorkflowServiceTrackingViewer  0.1

今回は、上記のうち、SignalR.WindowsAzureServiceBus を使用して、SignalR の Pub/Sub 方式の Scale Out について確認したいと思います。

補足 : 下記で述べるように、現時点 (2012/06) では、まだ、SignalR.WebSockets との共存はできませんので、ここでは、long polling を使用します。(今後のリリースを待ちたいと思います。)

 

環境構築

まず、いつものように、Visual Studio で Windows Azure プロジェクトを新規作成します。今回は、ASP.NET MVC 3 Web Role を追加します。

そして、NuGet を使い、SignalR.WindowsAzureServiceBus を取得します。

PM> install-package SignalR.WindowsAzureServiceBus

Attempting to resolve dependency 'SignalR.Server (>= 0.5.0)'.
Attempting to resolve dependency 'Newtonsoft.Json (>= 4.5.4)'.
Attempting to resolve dependency 'WindowsAzure.ServiceBus (>= 1.6.0.0)'.
Successfully installed 'Newtonsoft.Json 4.5.5'.
Successfully installed 'SignalR.Server 0.5.0'.
Successfully installed 'WindowsAzure.ServiceBus 1.6.0.0'.
Successfully installed 'SignalR.WindowsAzureServiceBus 0.1.0'.
Successfully added 'Newtonsoft.Json 4.5.5' to SignalrScaleTest.
Successfully added 'SignalR.Server 0.5.0' to SignalrScaleTest.
Successfully added 'WindowsAzure.ServiceBus 1.6.0.0' to SignalrScaleTest.
Successfully added 'SignalR.WindowsAzureServiceBus 0.1.0' to SignalrScaleTest.

上記でコンソールに表示されている依存バージョンに注意してください。
以前、こちらの投稿 (SignalR とクロス ブラウザーへの対応) でも記載したように、現在出ている SignalR.WebSockets 0.3 は、依存関係の問題で、SignalR.Server 0.4 以上との併用ができません。このため、現時点 (2012/06 時点) では、WebSocket transport を使って、この SignalR.WindowsAzureServiceBus は試せないので、今回は、long polling を使って動作を確認します。(WebSocket transport で試したい方は、もう少々お待ちください。)

なお、上記のパッケージ (および、依存するパッケージ) のインストールでは、サーバー側の最小限のパッケージしかインストールされていませんので、追加で SignalR.Hosting.AspNet、SignalR.JS (SignalR のクライアント ライブラリー) のパッケージもインストールします。

install-package SignalR.Hosting.AspNet
install-package SignalR.JS

 

アプリケーションのプログラミング

つぎに、プログラムを構築します。

今回は、下記の通り、「SignalR とクロス ブラウザーへの対応」で作成したコードとまったく同じコードを使用してみましょう。(プログラム コードの解説についても、「SignalR とクロス ブラウザーへの対応」を参照してください。)
今回は、特に、下記 太字の this.Clients に注意してください。this.Clients では、接続しているクライアントすべてのコレクションが取得されますが (実際の正体は、.NET のコレクション クラスではなく、dynamic 型です)、これはメモリー中のコレクションであるため、サーバーを複数台に負荷分散 (Load Balance) した場合は、問題になります。

[BetsHub.cs]

. . .
using SignalR.Hubs;
. . .

[HubName("betsgame")]
public class BetsHub : Hub
{
  public void CommitBet(
    string nickname, string target, int money)
  {
    this.Clients.notifybet(nickname, target, money);
  }
}

[Shared/_Layout.cshtml]

<!DOCTYPE html>
<html>
<head>
  <title>@ViewBag.Title</title>
  <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
  http://@Url.Content(
  http://@Url.Content(
  http://@Url.Content(
  @RenderSection("script", false)
</head>
. . .

[Home/Index.cshtml]

<h3>SignalR Demos !</h3>

Target:
<select id="target" size="1">
  <option value="spades">Spades</option>
  <option value="hearts">Hearts</option>
  <option value="clubs">Clubs</option>
  <option value="diamonds">Diamonds</option>
</select>

Tip:<input id="bets" type="number" />$

<br />

<button id="sendbutton">Bets !</button>

@section script {

  $(document).ready(function () {
    var nickname = prompt('What is your nickname ?');

    // setup hub client (signalR)
    var betsgame = $.connection.betsgame;

    betsgame.notifybet = function (nickname, target, money) {
      alert(nickname + ' bets '
        + money + '$ to ' + target + '.');
    };

    // connect to hub (signalR)
    $.connection.hub.start({}, function () {
      $('#sendbutton').click(function () {
        betsgame.commitBet(nickname, $('#target').val(), $('#bets').val());
      });
    });

  });

}

 

Windows Azure に配置した場合の動作確認 (SignalR.WindowsAzureServiceBus を使用しない場合)

では、SignalR.WindowsAzureServiceBus を使用しない場合の動きを確認してみましょう。上記の通り、SignalR.WindowsAzureServiceBus のパッケージがインストールされていますが、まだ必要な構成をおこなっていないため、このパッケージは使用されません。

まず、上記のプロジェクトを Windows Azure に配置するため、プロジェクトで、SignalR がコピーした下記の参照ライブラリーの [ローカル コピー] プロパティが「true」に設定されていることを確認してください。

Microsoft.ServiceBus.dll
Microsoft.Web.Infrastructure.dll
Newtonsoft.Json.dll
SignalR.dll
SignalR.Hosting.AspNet.dll
SignalR.Hosting.Common.dll
SignalR.WindowsAzureServiceBus.dll

さらに、今回、Windows Azure Load Balancer による Scaling の確認をおこなうので、インスタンス数を 2 以上に設定しておきましょう。Windows Azure プロジェクトの Web ロールを右クリックして [プロパティ] を選択し、下図の通り、Instance Count を 2 に変更しておきます。

プロジェクトを Windows Azure に配置します。

補足 : 本来なら、このあとで、再度 修正して配置しなおすので、Web deployment tool を使用して配置しておくと良いのですが、今回はインスタンス数が 2 以上なので、この手法は使用できません。(Web Deployment Tool を使うと、迅速に配置と確認ができます。)
まずは、ローカル環境 (Development Fabric) で、充分な動作確認をおこなっておきましょう。

では、ブラウザーを複数起動して、上記のサービスに接続してみましょう。
本来ならば、[Bets !] ボタンを押すと、下図の通り、接続しているすべてのブラウザーに通知がおこなわれます。(前述の通り、内部では、long polling が使用されます。)

しかし、実際には、このアプリケーションは期待通り動かないはずです。実際に動作させてみると、通知されるグループと通知されないグループ (非通知のグループ) の 2 つのグループが作成され、通知されるグループは毎回通知されますが、非通知のグループには、どのリクエストも通知されません。
そして、新しいブラウザー (クライアント) が この SignalR のサービスに接続するたびに、そのクライアントの所属グループが決定されます。一度同じグループになったものは、以降、ずっと同じグループに所属します。(例えば、ブラウザー A とブラウザー B が同じグループに所属した場合、以降は、常にこの 2 つは同じグループになります。)

このような誤った動作になる理由は明白です。前述の通り、Windows Azure 上に 2 つのインスタンス (仮想マシン) があるため、SignalR の通知先のコレクション (上記のプログラム コードの this.Clients) も 2 つのグループに分断され、上記のような動作結果となります。なお、Windows Azure では既定で Round Robin が使用されていますが (厳密には、Java のプロジェクトなどで、この既定のアフィニティを変更することもできます)、HTTP による接続や、Ajax の Request のたびに、別のサーバーに (交互に) 要求先が変更されるため、要求のタイミングによって接続先が決定されます。

 

SignalR.WindowsAzureServiceBus を使用した場合の動作確認

では、SignalR.WindowsAzureServiceBus を構成してみましょう。

まず、この SignalR.WindowsAzureServiceBus では、内部で、Windows Azure Service Bus のトピック (topic) を使用します。(ServiceBus topic をご存じない方は、「Windows Azure Service Bus の Topic を使用する」 で予習しておいてください。) このため、Windows Azure 管理ポータルを使用して、あらかじめ Windows Azure Service Bus の名前空間を作成し、発行者 (Account) とキー (Account Key) を取得します。

Glabal.asax.cs の Application_Start に、下記のコードを追加します。下記では、上記で取得した account、account key などを設定します。

. . .
using SignalR;
using SignalR.WindowsAzureServiceBus;
. . .

protected void Application_Start()
{
  . . .

  GlobalHost.DependencyResolver.UseWindowsAzureServiceBus(
    "{service bus namespace}", "{acount}", "{account key}", "test", 1);
}

なお、Service Bus の topic、subscription は、あらかじめ作成しておく必要はありません。プログラムが実行されると、{prefix path name}_{topic number} の名前 (上記コードの場合は「test_1」) で topic が作成されます。(なお、topic number には、1 以上の値を指定してください。)

以上で完了です。
上記のプロジェクトを Windows Azure に再配置すると、今度は、ちゃんと動きます ! (すべてのブラウザーは、リアルタイムに連携されます。)

なお、初回の動作で topic と subscription が作成されますが、この最初の作成の際だけ、かなり時間がかかるようなので、subscription がインスタンスの個数だけ作成されるまでウォーム アップ (warm-up) してから使用すると良いでしょう。(上図の通り、Windows Azure 管理ポータルを使って、作成状況が確認できます。インスタンス数が多いと、それだけ多くの subscription が作成されます。)

 

ここでは、SignalR.WindowsAzureServiceBus を使って、SignalR を Scale Out に対処させる手順を見ました。上記の通り、最新の SignalR では、Publish / Subscribe 型のサービス連携を開発者 (プログラマー) 自身が構築しなくても、Windows Azure Service Bus topic や Redis などの著名な Pub / Sub のサービスを使って Scale Out (Scaling) が行えます。
SignalR は、まだまだ進化していますので、是非、いろいろと動かしてみてください。

 

Windows Azure のトライアル (無償試用) について

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s