はじめ
に Web アプリケーションのようなステートレス環境では、セッション状態の概念を理解することは実際には意味がありません。それにもかかわらず、効果的な状態管理は、ほとんどの Web アプリケーションにとって必須の機能です。 Microsoft ASP.NET および他の多くのサーバー側プログラミング環境は、アプリケーションがユーザーごとおよびアプリケーションごとに永続的なデータを保存できるようにする抽象化レイヤーを提供します。
Web アプリケーションのセッション状態は、アプリケーションがさまざまなリクエストにわたってキャッシュおよび取得するデータであることに注意することが重要です。セッションは、サイトへの接続中にユーザーによって送信されたすべてのリクエストを表し、セッション状態は、セッション中にユーザーによって生成および消費された永続データのコレクションです。各セッションの状態は互いに独立しており、ユーザー セッションが終了すると存在しなくなります。
セッション状態は、HTTP プロトコルおよび仕様を構成する論理エンティティのいずれとも対応しません。セッションは、従来の ASP や ASP.NET などのサーバー側開発環境によって構築される抽象化レイヤーです。 ASP.NET がセッション状態を表示する方法、およびセッション状態が内部的に実装される方法は、プラットフォームのインフラストラクチャによって異なります。したがって、従来の ASP と ASP.NET はまったく異なる方法でセッション状態を実装しており、ASP.NET の次のバージョンではさらなる改善と機能強化が期待されています。
この記事では、ASP.NET 1.1 でセッション状態を実装する方法と、マネージド Web アプリケーションでセッション状態管理を最適化する方法について説明します。
ASP.NET セッション状態の概要
セッション状態は HTTP インフラストラクチャの一部ではありません。つまり、受信リクエストごとにセッション状態をバインドする構造コンポーネントが必要です。ランタイム環境 (従来の ASP または ASP.NET) は、Session などのキーワードを受け入れ、それを使用してサーバーに保存されているデータのブロックを示すことができます。 Session オブジェクトへの呼び出しを正常に解決するには、ランタイム環境は、処理中のリクエストの呼び出しコンテキストにセッション状態を追加する必要があります。これがどのように行われるかはプラットフォームによって異なりますが、これはステートフル Web アプリケーションの基本です。
従来の ASP では、セッション状態は、asp.dll ライブラリに含まれるフリースレッドの COM オブジェクトとして実装されます。 (興味がありませんか? このオブジェクトの CLSID は、実際には D97A6DA0-A865-11cf-83AF-00A0C90C2BD8 です。) このオブジェクトには、名前と値のペアのコレクションとして編成されたデータが格納されます。 「名前」プレースホルダーは情報を取得するために使用されるキーを表し、「値」プレースホルダーはセッション状態に格納される内容を表します。名前と値のペアはセッション ID ごとにグループ化されているため、各ユーザーには自分が作成した名前と値のペアのみが表示されます。
ASP.NET では、セッション状態のプログラミング インターフェイスは従来の ASP とほぼ同じです。ただし、それらの基本的な実装は完全に異なります。前者は後者よりも柔軟でスケーラブルで、強力なプログラミング機能を備えています。 ASP.NET セッション状態について詳しく説明する前に、ASP.NET セッション インフラストラクチャの構造上の機能の一部を簡単に確認してみましょう。
ASP.NET では、受信した HTTP 要求はすべて HTTP モジュールを介してパイプ処理されます。各モジュールは、リクエストによって運ばれる大量の情報をフィルタリングおよび変更できます。各リクエストに関連付けられた情報は「呼び出しコンテキスト」と呼ばれ、プログラミングでは HttpContext オブジェクトによって表されます。リクエスト コンテキストが提供する Items コレクションは単なるデータ コンテナですが、リクエスト コンテキストを状態情報の別のコンテナとして考える必要はありません。 HttpContext オブジェクトは、要求の処理に必要な時間を超える限られた有効期間を持つという点で、他のすべての状態オブジェクト (セッション、アプリケーション、キャッシュなど) とは異なります。リクエストが一連の登録された HTTP モジュールを通過すると、その HttpContext オブジェクトには状態オブジェクトへの参照が含まれます。最終的にリクエストを処理できるようになると、関連付けられた呼び出しコンテキストが特定のセッション (セッション) とグローバル状態オブジェクト (アプリケーションとキャッシュ) にバインドされます。
各ユーザーのセッション状態の設定を担当する HTTP モジュールは、SessionStateModule です。このモジュールの構造は、ASP.NET アプリケーションに多数のセッション状態関連のサービスを提供する IHttpModule インターフェイスに基づいて設計されています。セッション ID の生成、Cookie を使用しないセッション管理、外部状態プロバイダーからのセッション データの取得、リクエストの呼び出しコンテキストへのデータのバインドが含まれます。
HTTP モジュールはセッション データを内部に保存しません。セッション状態は常に「状態プロバイダー」と呼ばれる外部コンポーネントに保存されます。状態プロバイダーはセッション状態データを完全にカプセル化し、IStateClientManager インターフェイスのメソッドを通じて他の部分と通信します。セッション状態 HTTP モジュールは、このインターフェイス上のメソッドを呼び出して、セッション状態を読み取り、保存します。 ASP.NET 1.1 は、表 1 に示すように、3 つの異なる状態プロバイダーをサポートしています。
表 1: ステータス クライアント プロバイダー
プロバイダーの説明
InProc セッション値は、ASP.NET ワーカー プロセス (Microsoft® Windows Server® 2003 の aspnet_wp.exe または w3wp.exe) のメモリ内にアクティブなオブジェクトのままになります。これはデフォルトのオプションです。
StateServer セッション値はシリアル化され、別のプロセス (aspnet_state.exe) のメモリに保存されます。このプロセスは他のコンピュータでも実行できます。
SQLServer セッション値はシリアル化され、Microsoft® SQL Server® テーブルに保存されます。 SQL Server のインスタンスはローカルまたはリモートで実行できます。
セッション状態 HTTP モジュールは、現在選択されている状態プロバイダーを web.config ファイルの
上記のコードは、実際にローカル メモリ内の HTTP モジュールによって作成されたセッション値にアクセスします。特定の状態プロバイダーからデータを読み取ります (図 1 を参照)。他のページもセッション状態に同期的にアクセスしようとするとどうなりますか?この場合、現在のリクエストは、一貫性のないデータまたは古いデータの処理を停止する可能性があります。これを回避するために、セッション状態モジュールはリーダー/ライターのロック メカニズムを実装し、状態値へのアクセスをキューに入れます。セッション状態に対する書き込み権限を持つページは、リクエストが終了するまでそのセッションのライター ロックを保持します。 ページは、@Page ディレクティブの EnableSessionState プロパティを true に設定することで、セッション状態の書き込み権限を要求できます。 (これはデフォルト設定です)。ただし、EnableSessionState プロパティが ReadOnly に設定されている場合など、ページはセッション状態への読み取り専用アクセスを持つこともできます。この場合、モジュールは、そのページのリクエストが終了するまで、そのセッションのリーダー ロックを保持します。その結果、同時読み取りが発生します。 ページリクエストがリーダーロックを設定すると、同じセッション内の他の同時リクエストはセッション状態を更新できなくなりますが、少なくとも読み取りは可能になります。つまり、セッションに対して読み取り専用リクエストが現在処理されている場合、保留中の読み取り専用リクエストはフル アクセスを必要とするリクエストよりも高い優先順位を持ちます。ページ要求がセッション状態のライター ロックを設定すると、コンテンツの読み取りまたは書き込みに関係なく、他のすべてのページがブロックされます。たとえば、2 つのフレームが同時にセッションに書き込もうとした場合、一方のフレームは書き込みができるようになる前に、もう一方のフレームが完了するまで待機する必要があります。 状態プロバイダーの比較 デフォルトでは、ASP.NET アプリケーションはセッション状態をワーカー プロセスのメモリ、具体的には Cache オブジェクトの専用スロットに保存します。 InProc モードが選択されている場合、セッション状態は Cache オブジェクト内のスロットに保存されます。このスロットはプライベート スロットとしてマークされており、プログラムからアクセスすることはできません。つまり、ASP.NET データ キャッシュ内のすべての項目を列挙した場合、指定されたセッション状態に類似したオブジェクトは返されません。キャッシュ オブジェクトは、プライベート スロットとパブリック スロットという 2 種類のスロットを提供します。プログラマはパブリック スロットを追加して処理できますが、プライベート スロットはシステム (具体的には system.web パーツで定義されたクラス) によってのみ使用できます。 各アクティブなセッションの状態は、キャッシュ内の専用スロットを占有します。スロットの名前はセッション ID に基づいて付けられ、その値は SessionStateItem という名前の宣言されていない内部クラスのインスタンスです。 InProc 状態プロバイダーはセッション ID を取得し、キャッシュ内の対応する要素を取得します。その後、SessionStateItem オブジェクトの内容が HttpSessionState ディクショナリ オブジェクトに入力され、アプリケーションによって Session プロパティを通じてアクセスされます。 ASP.NET 1.0 には、Cache オブジェクトのプライベート スロットをプログラムで列挙可能にするバグがあることに注意してください。 ASP.NET 1.0 で次のコードを実行すると、現在アクティブな各セッション状態に含まれるオブジェクトに対応する項目を列挙できます。 foreach(キャッシュ内のDictionaryEntry要素) 、 キャッシュされたコンテンツを列挙するときに、システム スロットはリストされなくなります。 InProc はおそらくこれまでで最も高速なアクセス オプションです。ただし、セッションに保存されるデータが増えると、Web サーバーが消費するメモリも増え、パフォーマンスが低下するリスクが高まる可能性があることに注意してください。アウトプロセス ソリューションを使用する予定がある場合は、シリアル化と逆シリアル化によって起こり得る影響を慎重に検討する必要があります。アウトプロセス ソリューションでは、Windows NT サービス (aspnet_state.exe) または SQL Server テーブルを使用してセッション値を保存します。したがって、セッション状態は ASP.NET ワーカー プロセスの外側に残り、セッション状態と実際のストレージ メディアの間でシリアル化および逆シリアル化するには、追加のコード層が必要になります。これはリクエストが処理されるたびに発生するため、最大限に最適化する必要があります。 セッション データは外部リポジトリからローカル セッション ディクショナリにコピーする必要があるため、リクエストにより 15% (プロセス外) から 25% (SQL Server) の範囲でパフォーマンスが低下します。これは単なる概算ですが、最小の影響に近いはずであり、最大の影響はこれよりもはるかに大きくなることに注意してください。実際、この推定では、セッション状態に実際に保存される型の複雑さは完全には考慮されていません。 アウトプロセス ストレージ シナリオでは、セッション状態がより長く存続し、Microsoft® インターネット インフォメーション サービス (IIS) および ASP.NET の障害から保護されるため、アプリケーションがより強力になります。セッション状態をアプリケーションから分離することにより、既存のアプリケーションを Web ファームおよび Web ガーデン アーキテクチャに簡単に拡張することもできます。さらに、セッション状態は外部プロセスに保存されるため、プロセス ループによる定期的なデータ損失のリスクが本質的に排除されます。 ここでは Windows NT サービスの使用方法を説明します。前述したように、NT サービスは aspnet_state.exe という名前のプロセスで、通常は C:WINNTMicrosoft.NETFrameworkv1.1.4322 フォルダーにあります。 実際のディレクトリは、実際に実行している Microsoft® .NET Framework のバージョンによって異なります。状態サーバーを使用する前に、サービスが準備ができており、セッション ストレージ デバイスとして使用されるローカルまたはリモート コンピューター上で実行されていることを確認する必要があります。状態サービスは ASP.NET の一部であり、ASP.NET とともにインストールされるため、追加のインストーラーを実行する必要はありません。デフォルトでは、ステータス サービスは実行されていないため、手動で開始する必要があります。 ASP.NET アプリケーションは、ロードされた直後に状態サーバーへの接続を確立しようとします。したがって、サービスは準備ができて実行されている必要があります。そうでない場合は、HTTP 例外がスローされます。次の図は、サービスのプロパティ ダイアログ ボックスを示しています。 ASP.NET アプリケーションは、セッション状態サービスが配置されているコンピューターの TCP/IP アドレスを指定する必要があります。次の設定をアプリケーションの web.config ファイルに入力する必要があります。 <設定>; stateConnectionString 属性には、コンピュータの IP アドレスとデータ交換に使用されるポートが含まれます。デフォルトのコンピュータ アドレスは 127.0.0.1 (localhost)、デフォルトのポートは 42424 です。コンピュータを名前で示すこともできます。ローカルまたはリモート コンピューターの使用は、コードに対して完全に透過的です。 ASCII 以外の文字は名前に使用できず、ポート番号は必須であることに注意してください。 アウトプロセス セッション ストレージを使用する場合、ASP.NET ワーカー プロセスに何が起こっても、セッション状態は引き続き存在し、将来使用できます。サービスが中断された場合、データは保持され、サービスが復元されたときに自動的に取得されます。ただし、ステータス プロバイダー サービスが停止または失敗すると、データが失われます。アプリケーションを強力にしたい場合は、StateServer モードではなく SQLServer モードを使用してください。 <設定>; sqlConnectionString 属性を通じて接続文字列を指定できます。属性文字列にはユーザー ID、パスワード、サーバー名が含まれている必要があることに注意してください。この情報はデフォルトで固定名になっているため、データベースや初期カタログなどのタグを含めることはできません。ユーザー ID とパスワードは、統合されたセキュリティ設定に置き換えることができます。 データベースを作成するにはどうすればよいですか? ASP.NET には、データベース環境を構成するための 2 組のスクリプトが用意されています。最初のスクリプトのペアは、InstallSqlState.sql および UninstallSqlState.sql という名前で、セッション状態 NT サービスと同じフォルダーにあります。 ASPState という名前のデータベースといくつかのストアド プロシージャを作成します。ただし、データは SQL Server の一時記憶域である TempDB データベースに保存されます。これは、SQL Server コンピュータを再起動すると、セッション データが失われることを意味します。 この制限を回避するには、2 番目のスクリプトのペアを使用します。 2 番目のスクリプト ペアの名前は、InstallPersistSqlState.sql と UninstallPersistSqlState.sql です。この場合、ASPState データベースが作成されますが、テーブルは同じデータベース内に作成され、永続的になります。 SQL Server のセッション サポートをインストールすると、セッション状態データベース内の期限切れのセッションを削除するジョブも作成されます。ジョブは ASPState_Job_DeleteExpiredSessions という名前で、常に実行されています。このジョブが正しく動作するには、SQLServerAgent サービスが実行されている必要があることに注意してください。 どのモードを選択しても、セッション状態の操作をコーディングする方法は変わりません。通常どおり、いつでも Session プロパティを操作し、値の読み取りと書き込みを行うことができます。動作の違いはすべて、より低い抽象レベルで処理されます。状態のシリアル化は、おそらくセッション モード間の最も重要な違いです。 状態のシリアル化と逆シリアル化 イン プロセス モードを使用する場合、オブジェクトはそれぞれのクラスのアクティブなインスタンスとしてセッション状態に保存されます。実際のシリアル化と逆シリアル化が発生しない場合、作成したオブジェクト (シリアル化できないオブジェクトや COM オブジェクトを含む) を実際にセッションに保存でき、それらへのアクセスにそれほどコストがかからないことを意味します。アウトプロセス状態プロバイダーを選択する場合は、別の話になります。 アウトプロセス アーキテクチャでは、セッション値はローカル ストレージ メディア (外部 AppDomain データベース) から、リクエストを処理する AppDomain のメモリにコピーされます。このタスクを実行するにはシリアル化/逆シリアル化レイヤーが必要であり、プロセス外状態プロバイダーの主要なコストの 1 つとなります。この状況がコードに与える主な影響は、セッション ディクショナリに格納できるのはシリアル化可能なオブジェクトのみであることです。 ASP.NET では、関係するデータの種類に応じて 2 つの方法を使用してデータをシリアル化および逆シリアル化します。基本的な型の場合、ASP.NET は最適化された内部シリアライザーを使用します。オブジェクトやユーザー定義クラスなどの他の型の場合、ASP.NET は .NET バイナリ フォーマッタを使用します。基本タイプには、文字列、日時、ブール値、バイト、文字、およびすべての数値タイプが含まれます。これらの型の場合、カスタムのシリアライザーを使用した方が、デフォルトの共通 .NET バイナリ フォーマッタを使用するよりも高速です。 最適化されたシリアライザーは公開されておらず、文書化されていません。これは単なるバイナリ リーダー/ライターであり、シンプルだが効果的なストレージ アーキテクチャを使用しています。シリアライザーは、BinaryWriter クラスを使用して型のバイト表現を書き込み、次にその型に対応する値のバイト表現を書き込みます。シリアル化されたバイトを読み取る場合、クラスは最初にバイトを抽出し、読み取るデータ型を検出してから、BinaryReader クラスの型固有の ReadXxx メソッドを呼び出します。 ブール型と数値型のサイズはよく知られていますが、文字列のサイズは知られていないことに注意してください。基礎となるデータ ストリームでは、文字列には常に固定長 (一度に書き込まれる 7 ビット整数コード) が接頭辞として付けられ、リーダーはこの事実を利用して文字列の正しいサイズを決定します。日付値は、日付を構成するトークンの合計数のみを書き込むことによって保存されます。したがって、セッションをシリアル化するには、日付の型が Int64 である必要があります。 含まれるクラスがシリアル化可能としてマークされている限り、BinaryFormatter クラスを使用して、より複雑なオブジェクト (カスタム オブジェクトも同様) に対してシリアル化操作を実行できます。すべての非基本タイプは同じタイプ ID によって識別され、基本タイプと同じデータ ストリームに格納されます。全体的に、シリアル化操作により 15% ~ 25% のパフォーマンス低下が発生する可能性があります。ただし、これは基本型を使用した場合の概算値です。使用する型が複雑であればあるほど、オーバーヘッドが大きくなります。 プリミティブ型を広範囲に使用しない限り、効率的なセッション データ ストレージを実装することは困難です。したがって、少なくとも理論上は、3 つのセッション スロットを使用してオブジェクトの 3 つの異なる文字列プロパティを保存する方が、オブジェクト全体をシリアル化するよりも優れています。しかし、シリアル化したいオブジェクトに 100 個のプロパティが含まれている場合はどうなるでしょうか? 100 スロットを使用しますか? それとも 1 スロットだけ使用しますか?多くの場合、より良いアプローチは、複雑な型を複数の単純な型に変換することです。このアプローチは型コンバーターに基づいています。 「型コンバーター」は、型の主要なプロパティを文字列のコレクションとして返す軽量のシリアライザーです。型コンバーターは、属性を使用して基本クラスにバインドされる外部クラスです。どのプロパティをどのように保存するかは、タイプライターが決定します。型コンバーターは ViewState ストレージにも役立ち、バイナリ フォーマッタよりも効率的なセッション ストレージの方法を表します。 セッションのライフ サイクル ASP.NET セッション管理に関する重要な点は、セッション状態オブジェクトのライフ サイクルは、最初の項目がメモリ内ディクショナリに追加されたときにのみ開始されるということです。 ASP.NET セッションは、次のコード スニペットが実行された後にのみ開始されたとみなされます。 Session["MySlot"] = "Some data"; 通常、セッション ディクショナリにはオブジェクト タイプが含まれており、データを逆方向に読み取るには、戻り値をより具体的なタイプに変換する必要があります。 string data = (string) Session["MySlot"]; ページがデータをセッションに保存すると、値は HttpSessionState クラスに含まれる特別に作成された辞書クラスにロードされます。現在処理されているリクエストが完了すると、ディクショナリの内容が状態プロバイダーにロードされます。データがプログラムによってディクショナリに追加されなかったためにセッション状態が空の場合、データは記憶媒体にシリアル化されず、さらに重要なことに、ASP.NET キャッシュ、SQL Server、または NT State Services で提供されません。現在のセッションを追跡するためのスロット。これはパフォーマンス上の理由によるものですが、セッション ID の処理方法に重要な影響を及ぼします。セッション ディクショナリにデータが保存されるまで、リクエストごとに新しいセッション ID が生成されます。 セッション状態を処理中のリクエストに関連付ける必要がある場合、HTTP モジュールはセッション ID (開始リクエストでない場合) を取得し、構成された状態プロバイダーでそれを探します。データが返されない場合、HTTP モジュールはリクエストの新しいセッション ID を生成します。これは、次のページで簡単にテストできます: <%@ Page Language="C#" Trace="true" %>;
{
Response.Write(elem.Key + ": " + elem.Value.ToString());
図 2: [ASP.NET ステート サーバーのプロパティ] ダイアログ ボックス
<システム.ウェブ>;
<セッション状態
モード = "状態サーバー"
stateConnectionString="tcpip=expoware:42424" />;
</system.web>;
;
<システム.ウェブ>;
<セッション状態
モード="SQLサーバー"
sqlConnectionString="server=127.0.0.1;uid=<ユーザーID>;;pwd=<パスワード>;;" />;
</system.web>;
;
</html>;
<本文>;