Uncategorized

[T2-302 follow-up (1)] WCF の ASP.NET Compatibility Mode を使用した Stateful な N-tier アプリケーション (WCF による Load Balancing 全般の考察)

環境 :
Visual Studio 2008 (.NET Framework 3.5)

こんにちは。

毎年同じようなことを書いているようですみません。またまた Tech*Ed でデモの多くを取りこぼしましたので、デモ/説明ができなかった残り分を掲載したいと思います。(今、翌日のリハーサル待ちです . . .)

以下の順番で掲載していきます。

  1. WCF の ASP.NET Compatibility Model を使用した Stateful な N-tier アプリケーション (WCF による Load Balancing 全般の考察)
  2. WCF トレースの見方
  3. WF の Activity Execution Context (AEC) とクローンに要注意

あと、最後の BizTalk を組み合わせた内容は BizTalk の B の字もデモできませんでしたので、明日の T2-401 のセッションで Oracle Adapter やカスタムの Adapter を使ったデモとその仕組みの説明を実施しようと思います。(もしここも時間がなくなってしまったすみません。諦めてブログに掲載します . . .)

まず、最初に実施した Load Balancing のデモについてです。(セッションでは、Load Test をおこなってパフォーマンスやリソース消費の差をお見せする予定でしたが、タイムオーバーで無理でした . . .)

まず、本日デモでお見せした ASP.NET Compatibility Mode (ASP.NET 互換モード) による N-Tier の負荷分散対応のアプリケーション (Web UI) のサンプルプロジェクトを以下に置きます。

download sample

セッションでご紹介した Durable Service のデモ等については、構築手法をWeb キャストなどでもいくつかご紹介済みですのでここでの掲載は省略します。(例えば、「IT Pro 道場自主トレシリーズ アプリケーションの負荷分散の対応」 でご紹介しています。こちらの Web キャストの中でも説明していますし、本日もご説明しましたが、もともとは Load Balancing のための仕組みではないので、本日ご説明した点にご注意ください。)

この ASP.NET Comparibility Model のソースコードのエッセンスについては後述で説明をおこないますので、まずは、セッションでご紹介したかった ASP.NET Comparibility Mode、Durable Service、Workflow Service のすべての事例 (サンプルデモ) について内容や特徴をサマリーしておきます。(WCF の Stateful な load balancing 全体についての総括です。)

設計ポリシー

例えば、アプリケーション UI などの一時的な状態保持などは、本来 、ビジネス的な状態ではありませんので、画面側の処理などで実装し、むやみに WCF 側 (バックエンドロジック側) で実装しないようにしてください。例えば、上記でもご説明しているように、Workflow Service や Durable Service が持っている永続化は、もともと「ロードバランスのための仕組み」ではなく、ビジネスなどの状態永続化 (例: 終了しても続きから処理を継続できる、など) のための仕組みとして利用すべきでしょう。
本日ご紹介した ASP.NET Compatibility Mode、Durable Service、Workflow Service などは、こうした設計の考察上、WCF のロジックとして実装すべきいくつかのものに限定して使用するようにします。(さらに独自の永続化サービスの実装の是非など、詳細の設計へと進みます。)

こうした点を考慮せずむやみに使用すると、本日ご説明したようにスケールアウト (Load Balancing, プロセスの分割等) で問題が出てきますし、またその他のパフォーマンス上の問題を引き起こす場合もあるので注意してください。(例えば本日ご紹介した型なしの DataSet による WCF のパフォーマンス問題などは、UI 側で更新行を取得 (GetChanges) し、取得した内容を型指定された DataContract で Collection として渡すことで解決されるかもしれません。WCF は万能ではありませんので、WCF の振る舞いに合ったデザイン方針に配慮することで、より拡張性に優れたシステム構築が可能となります。)

スケーラビリティ

厳密にはシナリオに応じてさまざまですが、セッションでご紹介したように、Load Balancing を使用した場合の一般的なスケーラビリティについては、以下の順番になります。(Load Test などを実施すると非常によくわかります)

  1. ASP.NET Compatibility Mode
  2. Durable Service
  3. Workflow Service

背景ですが、まず、ASP.NET Competibility Mode では仕組み自体が light-weight であるという点もありますが、 プレーンな basic Http による接続など、設計面でもチューニングの幅を持たせることができます。 (ただし、netTcp などはサポートされないというデメリットも理解しておきましょう。また本日ご紹介したように、Durable Service などと異なり、ビジネスロジックにシステムロジックを混在させることにはなりますので、WCF ロジック内部でのモジュール化にも配慮する必要があります。)

一方、これらのうち、もっともオーバーヘッドの高いワークフローサービス (Workflow Service) では、永続化のために idle イベントを待機するなど (厳密には設定により異なりますが)、「ワークフロー」として永続化をおこなうためのメカニズムであるが故に、相応の余計な処理やオーバーヘッドが発生することになります。(一般的には問題ありませんが、むやみに使って大量な処理をおこなうと、簡単にメモリを消費させることになります。)
上述の通り、そもそも負荷分散を解決するためだけに使う仕組みではありませんなく、ワークフロー的な処理をおこなうためのものです。例えば、本日ご説明したように、処理が途中で中断された場合には、その内容が永続化されたままゴミとして残りますのでこうした配慮も必要です。他にも、Load Balancing の構成で複数ユーザで同じインスタンスにアクセスするシナリオなど、シナリオによっては標準の SqlWorkflowPersistenceService ではサポートされないケースがいくつかあります。メモリが溢れた際などあまりに遅いケースでは、1 つのアクティビティの処理実行後、次の idle までワークフローが永続化されずに次の要求が入り、error となるケースも考えられます。一般的なケースでは問題ありませんが、Workflow Service でこうした「厳密性」が必要になった場合は、要件にあわせてカスタムな永続化サービスを作成することも考慮に入れて使用しましょう。(カスタムな永続化サービスについてもデモをお見せできませんでしたが、ダウンロードセンターの WCF, WF, CardSpace Sample にもファイルに永続化をするサンプルコードなどがありますので、必要になった場合には、こうしたものを参考に自作してみると良いでしょう。Factory モデルを使用します。)

ASP.NET Compatibility Model サンプルコード (上記の添付) の説明

それでは、添付したダウンロードサンプルについて、コードのポイントを非常に簡単にご説明します。(ここはセッションの中で説明していますので詳細は省略します)

まずサービス側です。
ASP.NET Compatibility Mode の使用を宣言するため、AspNetCompatibilityRequirements 属性や、.config のaspNetCompatibilityEnabled 属性を設定しておくことで、下記のように HttpContext を使った永続情報の管理が可能となります。

[ServiceBehavior]

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]

public class CalcService : ICalcService

{

    public void SetValue(int value)

    {

        HttpContext.Current.Session[“calcValue”] = value;

    }

 

    public void AddValue(int value)

    {

        if (HttpContext.Current.Session[“calcValue”] == null)

            throw new FaultException(“SetValue が呼ばれてません);

 

        int calcValue = (int) HttpContext.Current.Session[“calcValue”];

        calcValue += value;

        HttpContext.Current.Session[“calcValue”] = calcValue;

    }

 

    public int GetValue()

    {

        if (HttpContext.Current.Session[“calcValue”] == null)

            throw new FaultException(“SetValue が呼ばれてません);

 

        return (int)HttpContext.Current.Session[“calcValue”];

    }

} 

<system.serviceModel>

              <serviceHostingEnvironment aspNetCompatibilityEnabled=true/>

              <services>

    以下、省略 . . . 

またサービス側では、セッションでもご説明したように、load balancing のための細かなデフォルト設定の変更もおこなっています。今回の場合は、basicHttpBinding (httpTransport バインディング要素) のサンプルになっていますですので、セッションの中でご説明したように下記の通り接続の維持を無効にしておきます。

<httpTransport keepAliveEnabled=false/> 

さて、ここからがポイントです。 (ここはちゃんと記載しておきましょう)

クライアント側ですが、ASP.NET Compatibility Mode では、サービスとの状態の維持のためにクッキー (Cookie) が使用されます (通常の ASP.NET のアプリ同様、Cookie でセッション ID の番号などを保持しています)。ですから、状態をクライアント側で維持しておきたい場合には、通常は、クライアント側の WCF の構成 (.config) などを設定して、httpTransport バインディング要素の allowCookies 属性を true にしておきます。この値を true にすると、セッションでご説明したように WCF のクライアントメカニズムが 状態を保持します。
しかし、今回の場合には、N-tier の Web アプリケーションですので、クライアント側のメカニズムも呼び出しごとに新しいものとなってしまいます (ブラウザから Request が出されるたびに、サーバ側の新しいスレッドが実行されます)。このため、こうした Web UI の N-tier のケースでは、Cookie の保持は自身で実装することになります。

したがって、まずは、このメカニズムを使用しないことを宣言するため、下記の通り allowCookies を false に設定しておきます。

<httpTransport keepAliveEnabled=false

               allowCookies=false/> 

そして、Cookie 保持のメカニズムを自前で実装したコード (Web UI 側のコード) が以下になります。考え方はセッションでご説明した通りです。(Web サービスの頃には、これを Cookiecontainer で保持していたと思いますが、WCF の場合には以下の通りになります。内部的には同じことです。)

protected void Button1_Click(object sender, EventArgs e)

{

    ChannelFactory<AspNetCompatiSvc.ICalcService> factory = new ChannelFactory<AspNetCompatiSvc.ICalcService>(“CustomBinding_ICalcService”);

    factory.Open();

    AspNetCompatiSvc.ICalcService channel = factory.CreateChannel();

 

    // ヘッダーを操作できるようにコンテキストスコープを設定

    using (OperationContextScope scope = new OperationContextScope(channel as IContextChannel))

    {

        channel.SetValue(int.Parse(TextBox1.Text));

 

        // Response ヘッダーの SetCookie の内容をセッションにコピー

        HttpResponseMessageProperty httpResponseProp = OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;

        Session[“WCF_SessionID”] = httpResponseProp.Headers[HttpResponseHeader.SetCookie];

    }

 

    factory.Close();

}

 

protected void Button2_Click(object sender, EventArgs e)

{

    ChannelFactory<AspNetCompatiSvc.ICalcService> factory = new ChannelFactory<AspNetCompatiSvc.ICalcService>(“CustomBinding_ICalcService”);

    factory.Open();

    AspNetCompatiSvc.ICalcService channel = factory.CreateChannel();

 

    // コンテキストスコープを設定

    using (OperationContextScope scope = new OperationContextScope(channel as IContextChannel))

    {

        // セッションから Cookie を取り出し Request ヘッダーに設定

        if (Session[“WCF_SessionID”] != null)

        {

            HttpRequestMessageProperty httpRequestProp = new HttpRequestMessageProperty();

            httpRequestProp.Headers.Add(HttpRequestHeader.Cookie, Session[“WCF_SessionID”] as string);

            OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequestProp);

        }

 

        channel.AddValue(int.Parse(TextBox2.Text));

    }

 

    factory.Close();

}

 

protected void Button3_Click(object sender, EventArgs e)

{

    ChannelFactory<AspNetCompatiSvc.ICalcService> factory = new ChannelFactory<AspNetCompatiSvc.ICalcService>(“CustomBinding_ICalcService”);

    factory.Open();

    AspNetCompatiSvc.ICalcService channel = factory.CreateChannel();

 

    // コンテキストスコープを設定

    using (OperationContextScope scope = new OperationContextScope(channel as IContextChannel))

    {

        // セッションから Cookie を取り出し Request ヘッダーに設定

        if (Session[“WCF_SessionID”] != null)

        {

            HttpRequestMessageProperty httpRequestProp = new HttpRequestMessageProperty();

            httpRequestProp.Headers.Add(HttpRequestHeader.Cookie, Session[“WCF_SessionID”] as string);

            OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequestProp);

        }

 

        TextBox3.Text = channel.GetValue().ToString();

    }

 

    factory.Close();

} 

WCF においてはコンテキスト (Context) と呼ばれる仮想的なオブジェクトを通してメッセージの Header 情報やメッセージの Body などの受け渡しをおこなっています。コンテキストの中を WCF の consumer (WCF サービス, WCF クライアント) から扱うには、上記のように OperationContextScope を作成し、このスコープ内でやりとりされたヘッダー情報などを参照することができます。
今回は Cookie の情報ですから、Web におけるプログラミングと同様、Response ヘッダーの「Set-Cookie」、及び Request ヘッダーの「Cookie」の中身を操作することになります。 WCF クライアントのメカニズムを使うのではなく、WCF のコンテキストから取り出したこの Cookie の値を Session の中に自身で保持しておくことで、次回以降も Session の中から同じ Cookie の値を取り出して処理され、WCF サービス側ではこのヘッダー情報をもとに同一のクライアントであることを認識します。

Categories: Uncategorized

Tagged as:

7 replies»

Leave a Reply