May 2012

Volume 27 Number 05

Windows Azure の内部 - SignalR を使ってパブリッシュ/サブスクライブ型のアプリケーションを実装する

Bruno Terkaly | May 2012

ブラウザベースの株式市場アプリケーションを作成し、できる限りリアルタイムに最新株式市場情報を表示するとします。現在普及しているすべてのブラウザーをサポートし、.NET や現在開発中のものなど、クライアントも多数サポートする予定です。加えて、多数のユーザーがオンラインでブラウザーを使ってもバックエンド サーバーの負荷がオーバーしないように、スケーラビリティを高くし、何百万人ものユーザーをサポートできるようにしなくてはなりません。このようなシナリオは、ブラウザベースのアプリケーションでは、非常に困難な事例になります。スケーラブルなパブリッシュ/サブスクライブ型のシステムは、実装が難しいことで知られています。

SignalR は、実行時間が長いポーリングを実用的かつスケーラブルにするマイクロソフトの新しいテクノロジで、パブリッシュ/サブスクライブ型のシステムを実装する際に起きる問題に対処する待望のソリューションです。GitHub からダウンロードできるこのフレームワークを利用すると、リアルタイムに長時間接続を保持する、非同期でスケーラブルな Web アプリケーションを簡単に作成できます。SignalR は、ここ数年の多くの技術革新によって可能になりました。SignalR 作成者の David Fowler と Damian Edwards は、数年前だったら実装するのは非常に困難だったと言うでしょう。マルチスレッド プログラムの作成は、.NET Framework 4 の Task Parallel Library が導入されるまではきわめて難しい作業でした。また、jQuery が開発されたことによって、JavaScript をいったん記述すれば、それを Firefox、Internet Explorer、Safari、Opera、および Chrome に配置できるようになりました。こうしたさまざまなテクノロジが集まり、SignalR の作成と使用が技術的に可能になりました。

SignalR アプリケーションを作成してクラウドでホストする方法について説明する前に、まず、従来のポーリングの手法固有の問題を振り返ってみましょう。

AJAX ポーリングと実行時間が長いポーリング

これまで、サーバーからの最新情報をブラウザーに反映する場合、ある定義済みの間隔 (1 秒ごと、2 秒ごとなど) でサーバーにポーリングしていました。一般に、ユーザーの数が少なければ、この手法でも適切に機能します。ただし、ユーザーが数十人以上になると、定期的にポーリングすることは非効率になり、サーバーのパフォーマンスに影響を与えます。しかし、Web は http を基盤とし、http はステートレスで、ブラウザベースの Web 要求に対して毎回接続と切断を繰り返すため、長い間ポーリングが使用されてきました。要するに、http は要求/応答の概念に基づいて設計されていて、ブラウザーから何かを "要求"しない限り、何も得られません。つまり、問題は「ポーリングのメカニズムを利用しないで、サーバーからクライアント ブラウザーに変更を送信する方法はあるか」ということです。

いくつか制限がありますが、AJAX ポーリングは広く使用されていて、データが変更される間隔がわかっている場合は適切に機能します。残念ながら、更新の頻度がわかることはめったにありません。鍵となるのは、更新の頻度を調整し、サーバーの負荷と、ユーザーに報告される情報の適時性の間のバランスをとることです。AJAX ポーリングは、サーバーでイベントが発生するタイミングとクライアントに通知されるタイミングにずれがあるため、(株価情報の更新など) シナリオによっては十分とは言えない場合があります。

実行時間の長いポーリングでは、クライアントが要求を行い、接続を保持したまま、サーバーからの応答を待機します。クライアントは、サーバーから有効な応答が返ってきたときのみ、接続を終了します。クライアントにとっては接続状態を保持したままでも問題ありませんが、サーバーにとってはリソースの不足につながります。サーバーは、開いている接続ごとに 1 つまたは複数のスレッドを管理する必要があるため、接続を保持すると使用可能なスレッド数が少なくなります。サーバーで使用できるスレッド数が少なくなると、スケーラビリティが低下し、別のクライアント要求への応答が遅くなります。実行時間が長いポーリングを実用的にするには、サーバーにスレッド管理を実装する方法とそれをメンテナンスする方法について注意深く検討する必要があります。実行時間が長いポーリングは、サーバーのスレッドを消費するため、スケーラビリティに問題が生じる可能性があります。

SignalR アプリケーションを作成してクラウドに配置する

SignalR は、パブリッシュ/サブスクライブ型モデルのサポートに、新たなアプローチを採用しています。SignalR が 1 台のサーバーで何十万もの接続をサポートできるとしても、今後さらなる拡張が必要になることもあります。マイクロソフトが提供するクラウドである Windows Azure は、SignalR アプリケーションのホスト、配置、および拡張を簡単に行えるようにします。

そこで、SignalR アプリケーションの作成とクラウドへの配置をどの程度迅速に実行できるかを紹介します。必要なツールと Windows Azure に関連する SDK はインストール済みであると仮定していますが、セットアップの詳細についてのガイダンスが必要な方は、私のブログ記事をご覧ください。

まず、ブラウザーがドラッグ操作を別のブラウザーにブロードキャストするアプリケーションを実装する方法について説明します。つまり、ユーザーがブラウザーでなんらかの図形をドラッグすると、別のブラウザーでもその図形が動きます。Browser #1 が灰色の図形を動かすと (図 1 参照)、その図形が自動的に Browser #2 で動きます。

動作しているパブリッシュ/サブスクライブ型のモデル
図 1 動作しているパブリッシュ/サブスクライブ型のモデル

ここではすべての画面ショットと詳細手順を示すスペースがないので、詳しくは、私のブログでご覧ください。

SignalR アプリケーションを作成してクラウドでホストする、大まかな手順を以下に示します。

  1. Visual Studio 2010 を使用して新しいクラウド プロジェクトを作成します。
  2. プロジェクトに ASP.NET Web ロールを追加します。これが、Web アプリケーションのコンテナーになります。
  3. NuGet を使用して、必要な SignalR アセンブリと参照、さらに jQuery JavaScript モジュールをいくつか追加します。
  4. SignalR 固有のハブから継承するサーバー側クラスを作成します。これは、Browser #1 をリッスンしてイベントを Browser #2 に転送するサーバー側コードです。
  5. 本質的に JavaScript と Web ページから成るクライアント側コードを作成します。
  6. Windows Azure 管理ポータルにアクセスして、ホストするサービスとストレージ アカウントを作成します。
  7. プロジェクト (手順 1. ~ 4.) をパッケージ化して Windows Azure に配置します。
  8. Windows Azure で、実行中のインスタンスがいくつ必要かを指定します (ここが高い拡張性を示す部分です)。必要な場合は、配置後に実行中のインスタンス数を調整できます。
  9. これだけです。

図 2 に、完成時の Visual Studio 2010 ソリューションを示します。

Visual Studio での完成した SignalR ソリューション
図 2 Visual Studio での完成した SignalR ソリューション

Visual Studio ソリューションに追加される 3 つの重要なファイルを表 1 に示します。

表 1 SignalR を実現するモジュール

モジュール 説明
MoveShape.cs このサーバー側 SignalR ファイルは、jQuery 呼び出しをブロードキャストするためのハブまたは中央ゲートウェイとして動作します。このコードは、ブラウザーをリッスンして、ユーザーの操作を別のブラウザーに転送します。
MoveShape.js ブラウザーにおけるこの JavaScript は、SignalR バックエンド サーバーを通じて呼び出されます。
MoveShape.htm この Web ページは、すべての操作が行われる場所です。つまり、ユーザーが図形をドラッグし、別のユーザーが自身のブラウザーでその図形が動くのを確認する場所になります。

あるブラウザーをリッスンして別のブラウザーにブロードキャストするサーバー側コード (手順 4.) を 図 3 に示します。

図 3 MoveShape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
// Add reference
using SignalR.Hubs;
namespace CloudSignalRSample_WebRole
{
    // Derive from Hub, which is a server-side class
    // and a client side proxy.
    [HubName("moveShape")]  // moveShape is the name used in the JavaScript
    public class MoveShapeHub : Hub
    {
        public void MoveShape(int x, int y)
        {
            // Broadcast to all connected browsers
            Clients.shapeMoved(Context.ConnectionId, x, y);
            // Simple diagnostics for debugging
            System.Diagnostics.Debug.WriteLine("x = " + x + ", y = " + y);
        }
    }
}

ドラッグ操作を別のブラウザーにブロードキャストするクライアント側の Web ページのコード (手順 5.) を図 4 に示します。これは基本的に JavaScript ファイルと Web ページです。Web ページでは MoveShape.js ファイルをインクルードします。コードで定義されている図形は、jQuery JavaScript を使用してドラッグされます。

図 4 MoveShape.htm

<!DOCTYPE html >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <style type="text/css">
        #shape
        {  
            width: 200px;
            height: 200px;
            background: #ccc;
            border: 2px solid #333;
        }
        p { margin-left:10px; }
    </style>
</head>
<body>
    <div id="shape"></div>
    <script src="../Scripts/jquery-1.6.4.js" type="text/javascript"></script>
    <script src="../Scripts/jquery-ui-1.8.18.js" type="text/javascript"></script>
    <script src="../Scripts/jquery.signalR.js" type="text/javascript"></script>
    <script src="/signalr/hubs" type="text/javascript"></script>
    <script src="MoveShape.js" type="text/javascript"></script>
    <div>  <p>Hello</p></div><p></p>
</body>
</html>

ブロードキャストを行ってドラッグ イベントを受け取るクライアント側の JavaScript コードを図 5 に示します。このコードは、Windows Azure によってホストされるサービスの "hub" (ハブ) と通信します。次のように、このコードは別のブラウザーからドラッグ イベントを受け取ることができ、コードに基づいている固有のドラッグ イベントには応答しません。

if ($.connection.hub.hid !== cid) {

図 5 MoveShape.js

/// <reference path="Scripts/jquery-1.6.4.js" />
/// <reference path="Scripts/jquery.signalR.js" />
/// <reference path="Scripts/jquery-ui-1.8.18.js" />
$(function () {
    // Get a reference to the server-side moveShape() class.
    var hub = $.connection.moveShape,
    // Get a reference to the shape div in the html
    $shape = $("#shape");
    // Use extend to move the shape object (if we are not the sender)
    $.extend(hub, {
        // Use css to move the shape object
        shapeMoved: function (cid, x, y) {
            if ($.connection.hub.id !== cid) {
                $shape.css({ left: x, top: y });
                $("p:last").text("left: " + x + ", top: " + y);
            }
        }
    });
    // Wire up the draggable behavior (when hub is done starting)
    // "done" is a jQuery deferred method
    $.connection.hub.start().done(function () {
        $shape.draggable({
            // Implement draggable effect for jQuery
            drag: function () {
                // Tell the server that the shape was just dragged
                hub.moveShape(this.offsetLeft, this.offsetTop);
            }
        });
    })
});

普及しているすべてのブラウザーで機能する、非同期でスケーラブルな Web アプリケーションを作成することは困難でしたが、SignalR は、その技術障壁を少なくする大きな一歩です。SignalR を Windows Azure と組み合わせて使用すると、サーバーから頻繁な更新を必要とするさまざまなアプリケーションの可能性が広がります。また、従来のデスクトップ アプリケーションなど、ブラウザー以外のクライアントでも機能します。さらに、WebSocket、サーバー送信イベント、Internet Explorer の固定フレームなど、実行時間の長いポーリングに基づいていない、その他の高レベルなトランスポートを利用するよう設計されています。

今後、マイクロソフトは、正式に SignalR を Microsoft Web Platform の一部に統合することを計画しているため、今こそ GitHub でこのリリースを試すときです。マイクロソフトは、SignalR を .NET Framework に組み込むのではなく、モデル ビュー コントローラー、Web API、Web ページなど、公式にサポートする帯域外プロジェクトとしてリリースすることが予想されます。SignalR は、ブラウザベースのアプリケーションを絶えず最新のサーバーベースの情報に更新する方法を一新する可能性を秘めています。非同期でスケーラブルなクロスブラウザー対応の Web アプリケーションの作成を困難にしていた大きな障害は、今や大幅に軽減されました。

Bruno Terkaly は、マイクロソフトの開発者エバンジェリストとして活動しています。彼の深い知識は、多数のプラットフォーム、言語、フレームワーク、SDK、ライブラリ、および API を使用してコードを記述してきた、この分野での何年にもわたる経験に培われています。普段は、コードの記述やブログ投稿のほか、(特に Windows Azure プラットフォームを使用して) クラウドベースのアプリケーションを作成することに関するライブ プレゼンテーションを行っています。

この記事のレビューに協力してくれた技術スタッフの David Fowler に心より感謝いたします。