-
はじめに
前回は、Queueストレージを利用したWebロールとWorkerロールの疎結合型の連携についてご紹介しました。
スケーラビリティを考慮すると、WebロールとWorkerロールは疎結合である方が望ましいのですが、利用形態によってはリアルタイムに実行結果を入手したいケースもあります。
1つの解決手段として、ネットワークを通じたコミュニケーションがあります。
たとえば、WebサービスやWCFサービスをWorkerロールでホストしておき、WebサービスがこのサービスにアクセスしてWorkerロールにリクエストを発行するといったソリューションがあります。
そのため、通常ではネットワークによる外部とのコミュニケーションが想定されていないWorkerロールにおいてもHTTPおよびTCPのポートを開放し、外部からのリクエストを受け付けることが可能になっています。
-
3種類のエンドポイント定義
クラウドサービスで公開することのできるエンドポイントには3種類の公開形式があります。
1つは、ロードバランサを経由して公開されるInputエンドポイントです。
広くサービスを公開する場合や、負荷分散をしながらサービスを稼働させたい場合にはこのエンドポイントを選択します。
もう1つはInstance Inputエンドポイント。複数のインスタンスが稼働している場合、特定のインスタンスと直接的なコミュニケーションを実行したい場合に利用します。
このエンドポイントは特定のインスタンスに障害が発生してしまうと、サービスの提供が中断してしまう場合がありますので、特段の必要がない限りInputエンドポイントを利用する事をお勧めします。
なお、リモートデスクトップ接続のポートはInstance Inputで公開されています。
最後のポートはInternalです。読んで字のごとく、クラウドサービス内部のコミュニケーションに利用します。
Internalポートは同一のパッケージに含まれているロール間のみ利用可能なポートであり、外部からアクセスすることはできません。また、エンドポイントはロードバランスされません。
今回は、Internalエンドポイントを利用して複数のWorkerロールインスタンスを呼び出すサンプルを作成したいと思います。
そこで、WebロールおよびWorkerロールを1つずつ含むクラウドプロジェクトを作成します。
-
Workerロールの準備と実装
まずはWorkerロール側の作業を進めます。
最初に、WorkerロールにWCFサービスを追加します。
ソリューションエクスプローラーでWorkerロールプロジェクトのコンテキストメニューを表示し、「追加」→「新しい項目」を選択します。
ダイアログから「WCFサービス」を選択し、任意の名前を付けてサービスを作成します。この例ではMyService1という名前でサービスを作成しています。

次に、App.configを開き<system.serviceModel>で囲まれた範囲を削除しておきます。
今回は任意のTCPエンドポイントを作成するため、この定義は必要ありません。
そして、TCPエンドポイントを定義するために、Workerロールのプロパティを開きます。
「エンドポイント」タブを選択し「エンドポイントの追加」をクリックし新たなエンドポイントを追加します。

今回は、「MessageEP」という名前でInternalエンドポイントをTCPプロトコルで構成します。
続いて、Workerロールインプリメントをしていきます。
まず、usingディレクティブにSystem.ServiceRuntimeの宣言を追加しておきます。
次に、サービスを起動するためのメソッドを追加します。
private static void StartService()
{
ServiceHost serviceHost;
//netTCPBindingでWCFサービスを公開
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
//エンドポイント情報の取得
RoleInstanceEndpoint ServiceEndPoint =
RoleEnvironment.CurrentRoleInstance.InstanceEndpoints[“MessageEP”];
string EndpointUrl
= String.Format(“net.tcp://{0}/MyService1”, ServiceEndPoint.IPEndpoint);
//ServiceHostの生成
serviceHost = new ServiceHost(typeof(MyService1));
//エンドポイントの追加
var ep = serviceHost.AddServiceEndpoint(
typeof(IMyService1),binding, EndpointUrl);
Trace.WriteLine(String.Format(“Port Opend #:{0}”, EndpointUrl), “Information”);
//ServiceHostのOpen
serviceHost.Open();
}
サービスをホストする流れは、一般的なWCFサービスと同一です。ただし、エンドポイントの取得方法だけ独特のものとなります。
エンドポイントはRoleEnvironment.CurrentRoleInstanceのInstanceEndpointsコレクションの中から選択する必要があります。
InstanceEndpointsはKey/Valueのコレクションになっており、先ほど定義した「MessageEP」のキーでエンドポイントのアドレスを取得します。
このようにすることで、各インスタンスに与えられたアドレスおよびポートを正しく取得することが可能になります。
続いて、Runメソッドを以下のように書き換えます。public override void Run()
{StartService();
while (true)
{
Thread.Sleep(10000);
}
}
Workerロール本体の修正はこれで完了です。
なお、今回のサンプルではWCFサービスのサービスコントラクトにWorkerロールのインスタンスIDをstring型で返すメソッドを記述しています。[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
public class MyService1 : IMyService1
{public string GetInstanceInfo()
{
return RoleEnvironment.CurrentRoleInstance.Id;
}
}
この中で注意すべき点は、[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]の定義を追加しておくことです。
これを忘れると、エンドポイントの解決に失敗し、正しくサービスに接続できません。
最後に、インターフェイスのサービスコントラクト定義も書き換えておきます。
これでWorkerロール側のインプリメントは完了です。 -
Webロールの準備と実装
次にWebロールの設定とインプリメントを進めていきましょう。
最初に、WCFサービスのインターフェイスをインポートします。
ソリューションエクスプローラーでWebロールプロジェクトのコンテキストメニューを表示し、「追加」→「既存の項目」を選択し、Workerロールプロジェクト内に存在するインターフェイス定義のコードをインポートします。
インポートしたコードを開き、usingディレクティブにSystem.ServiceRuntime.Channelsの宣言を追加します。
次に、元のインターフェイスと、IServiceChannelを継承したカスタムのチャネルインターフェイスを定義します。
以下の例では、IMyService1とIServiceChannelを多重継承したIMyService1Channelインタフェースを定義しています。
public interface IMyservice1Channel : IMyService1, IServiceChannel { }
続いて、WebのUIをデザインします。
以下のようにボタンとGridViewを持つフォームを用意しておきます。

続いてコードビハインドのusingディレクティブに以下宣言を追加します。using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using RecieveWorkerRole;
次に、以下のようなメソッドを追加します。
private List<string> GetWorkerRoleInfo()
{List<string> workerinfo = new List<string>();
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
foreach (var instance in RoleEnvironment.Roles[“RecieveWorkerRole”].Instances)
{
//クライアントチャネルファクトリを生成
string Serviceuri = String.Format(“net.tcp://{0}/MyService1”,
instance.InstanceEndpoints[“MessageEP”].IPEndpoint.ToString());
ChannelFactory<IMyservice1Channel> channelFactory
= new ChannelFactory<IMyservice1Channel>(binding, Serviceuri);
//クライアントチャネルの生成とOpen
IMyservice1Channel channel = channelFactory.CreateChannel();
channel.Open();
workerinfo.Add(channel.GetInstanceInfo());
channel.Close();
}
return workerinfo;
}
このメソッドはWorkerロールの各インスタンスに接続し、それぞれのインスタンスのIDを取得しListに追加するメソッドです。
Workerロールの各インスタンスの情報(RoleInstance型)は、RoleEnvironment.Roles[“RecieveWorkerRole”].Instancesにコレクションされており、foreachループで順番にインスタンス情報を読み出しています。
エンドポイント情報はRoleInstanceのInstanceEndpointsにコレクションされています。
Workerロール同様、「MessageEP」エンドポイントを抽出し、参照先のエンドポイントとします。
サービスへ接続するためのクライアントチャネルはChannelFactoryのCreateChannelを実行することで作成します。
その後、クライアントチャネルをOpenして接続を開始し、インターフェイスのGetInstanceInfoメソッドをCallしています。
続いて、クライアントチャネルをCloseして完了です。
最後に、ボタンクリックのイベントハンドラ内部を以下のように書き換えます。protected
void btnGetWorkerInfo_Click(object sender, EventArgs e)
{grdWorkerRoles.DataSource = GetWorkerRoleInfo();
grdWorkerRoles.DataBind();
}
GridViewのDataSourceに先ほど記述したメソッドの結果を設定し、DataBindを実行してデータバインディングを実現します。
これでWebロール側のインプリメントも完了です。 -
実行してみる
それでは、実際にコードを実行してみましょう。
その前に、Workerロールのインスタンス数を2インスタンス以上に設定し、コンピュートエミュレーターにてデバッグ実行を開始します。
Webブラウザが起動し、ボタンをクリックしてみましょう。
正しく実行されれば、以下のようにWorkerロールのインスタンスIDの一覧が表示されるはずです。

今回のサンプルでは、Internalエンドポイントを利用しましたが、Inputエンドポイントを利用する場合には、クライアント側はWorkerロールのURLとポートを文字列で指定することで、接続可能です。
たとえば、Workerロールのエンドポイントが3050番ポートを利用する場合には「net.tcp://localhost:3050/MyService1」が接続先になります。
また、クラウドサービスにデプロイする際には、「net.tcp://[デプロイ先のクラウドサービス].cloudapp.net:3050/MyService1」が接続先となります。
Workerロールは、Input & Internalのいずれの場合でも同一のインプリメントで実行することが可能です。
Comments are closed here.