Tomcatがセッションを管理する方法の例

Tomcatがセッションを管理する方法の例

ConcurrentHashMapを学習しましたが、どのように適用すればよいかわかりませんか? Tomcat のセッションを使用していますが、その実装方法がわかりません。セッションはどのように作成および破棄されるのでしょうか?読み進めれば分かります。

セッション構造

さっそく写真をご覧ください

上の図をよく見ると、次のような結論が導き出されます。

  • HttpSessionはJavaEE標準のSessionを操作するためのインターフェースクラスなので、実際にはStandardSessionFacadeクラスを操作することになります。
  • Sessionがデータを保存するために使用するデータ構造はConcurrentHashMapです。図に示すように、 Sessionに msg が保存されています。

ConcurrentHashMapを使用する必要があるのはなぜですか?その理由は、HTTP リクエストを処理するときに、1 つのスレッドだけがセッションにアクセスするわけではないからです。最近の Web アプリケーションでは、ページにアクセスするときに複数のリクエストを同時に実行する必要があり、これらのリクエストは Web コンテナ内の異なるスレッドによって同時に実行される可能性があります。そのため、 HashMapを使用すると、スレッドの安全性の問題が起こりやすくなります。

まず、HttpSession ラッパー クラスを見てみましょう。

標準セッションファサード

このクラスでは、外観パターン(Facde)の実践的な応用を学ぶことができます。その定義は以下の通りです。

パブリッククラスStandardSessionFacadeはHttpSessionを実装します

では、このクラスはどのようにして Session 機能を実装するのでしょうか。次のコードから、このクラスが HttpSession の実際の実装クラスではなく、実際の HttpSession 実装クラスのラッパーであり、デザイン パターンのファサード モードである HttpSession インターフェイスのメソッドのみを公開していることは簡単にわかります。

 プライベート最終HttpSessionセッション;
 パブリックStandardSessionFacade(HttpSessionセッション) {
 this.session = セッション;
 }

では、HttpSession 実装クラスを使用しないのはなぜでしょうか?

図 1 によると、 HttpSession の実際の実装クラスはStandardSessionであることがわかります。プログラムではなく Tomcat によって呼び出される必要があるメソッドがこのクラスで定義されていると仮定すると、Java 型システムにより、このクラスを直接操作できるようになりますが、次のコードに示すように、予期しない問題が発生します。

StandardSession別のレイヤーでラップすると、上記のコードの実行時にエラーが発生します。下の図に示すように、型変換例外がスローされ、ここでの不正な操作が防止されます。

さらに、ファサード クラスを経由せずにStandardSession直接アクセスすることはできますか?

実際、それは可能です。リフレクション メカニズムを通じてStandardSession取得できますが、何をしているのかを理解しておく必要があります。コードは次のとおりです

 @GetMapping("/s")
 パブリック String sessionTest(HttpSession httpSession) は、ClassNotFoundException、NoSuchFieldException、IllegalAccessException をスローします {
 StandardSessionFacade セッション = (StandardSessionFacade) httpSession;
 クラス targetClass = Class.forName(session.getClass().getName());

 // 可視性を変更する フィールド standardSessionField = targetClass.getDeclaredField("session");
 標準セッションフィールドにアクセス可能(true)を設定します。
 //StandardSession を取得します。 standardSession = (StandardSession) standardSessionField.get(session);
 
 standardSession.getManager().toString() を返します。
 }

標準セッション

このクラスの定義は次のとおりです

パブリッククラスStandardSessionは実装します 
HttpSession、セッション、シリアル化可能

インターフェースを通じて、JavaEE 標準のHttpSessionに必要な機能に加えて、このクラスにはシリアル化機能もあることがわかります。

図 1 では、 StandardSessionConcurrentHashMapを使用してデータを保存することが既にわかっているので、次に、 StandardSessionのシリアル化と逆シリアル化の実装と、リスナーの機能に焦点を当てます。

シリアル化

前のセクションで、リフレクション メカニズムを通じてStandardSessionを取得したことを覚えていますか?次のコードを使用すると、逆シリアル化されたStandardSessionどのように見えるかを直接確認できます。

 @GetMapping("/s")
 パブリック void sessionTest(HttpSession httpSession、HttpServletResponse 応答) は、ClassNotFoundException、NoSuchFieldException、IllegalAccessException、IOException をスローします {
 StandardSessionFacade セッション = (StandardSessionFacade) httpSession;
 クラス targetClass = Class.forName(session.getClass().getName());

 // 可視性を変更する フィールド standardSessionField = targetClass.getDeclaredField("session");
 標準セッションフィールドにアクセス可能(true)を設定します。
 //StandardSession を取得します。 standardSession = (StandardSession) standardSessionField.get(session);
 
 //観察のためにいくつかのデータを保存しますstandardSession.setAttribute("msg","hello,world");
 standardSession.setAttribute("user","kesan");
 standardSession.setAttribute("パスワード", "いいね");
 standardSession.setAttribute("tel", 10086L);
 //シリアル化された結果を Http レスポンスに直接書き込みます。ObjectOutputStream objectOutputStream = new ObjectOutputStream(response.getOutputStream());
 
 standardSession.writeObjectData(オブジェクト出力ストリーム);
 }

何も問題がなければ、このインターフェースにアクセスするブラウザはダウンロード操作を実行し、最終的にファイルを取得します。

WinHex使用して開いて分析します。図はシリアル化後の結果を示しています。主に多くのセパレータと、図の赤いボックス内の標準情報などの型情報と値が含まれています。

シリアル化されたファイルがどのようにデータを整理するかを調べることはあまり意味がないので、お勧めしません。

本当に興味があるなら、次のコードorg.apache.catalina.session.StandardSession.doWriteObjectを読むことをお勧めします。

リスナー

JavaEE 標準では、 HttpSessionAttributeListenerを構成することでセッションの変更を監視できますが、 StandardSessionではどのように実装されるのでしょうか。オブザーバー パターンを理解している場合は、おそらくすでに答えがわかっているでしょう。 setAttribute を例にとると、このメソッドを呼び出した後、処理のためにこのスレッドでリスナー メソッドがすぐに呼び出されます。つまり、リスナーで長時間ブロックされる操作を実行すべきではありません。

 パブリック void setAttribute(文字列名、オブジェクト値、ブール値通知) {
 //無関係なコードを省略//上記で構成されたイベントリスナーを取得しますObject listeners[] = context.getApplicationEventListeners();
 if (リスナー == null) {
  戻る;
 }
 (int i = 0; i < listeners.length; i++) {
  //HttpSessionAttributeListener のみが実行可能 if (!(listeners[i] instanceof HttpSessionAttributeListener)) {
  続く;
  }
  HttpSessionAttributeListener リスナー = (HttpSessionAttributeListener) listeners[i];
  試す {
  //現在のスレッドでリスナーの処理メソッドを呼び出す if (unbound != null) {
   if (unbound != value || manager.getNotifyAttributeListenerOnUnchangedValue()) {
   // キーの値が変更された場合は、リスナーの attributeReplaced メソッドを呼び出します。context.fireContainerEvent("beforeSessionAttributeReplaced", listener);
   if (イベント == null) {
    イベント = 新しい HttpSessionBindingEvent(getSession(), name, unbound);
   }
   リスナー.attributeReplaced(イベント);
   context.fireContainerEvent("afterSessionAttributeReplaced", リスナー);
   }
  } それ以外 {
   //新しいキーが追加された場合は、attributeAdded メソッドを実行します。context.fireContainerEvent("beforeSessionAttributeAdded", listener);
   if (イベント == null) {
   イベント = 新しい HttpSessionBindingEvent(getSession(), 名前、値);
   }
   リスナー.attributeAdded(イベント);
   context.fireContainerEvent("afterSessionAttributeAdded", リスナー);
  }
  } キャッチ (Throwable t) {
  //例外処理}
 }
 }

セッションライフサイクル

セッションを保存する方法

Sessionの構造を理解した上で、 StandardSessionがいつ作成されるのか、どのような点に注意する必要があるのか​​を明確にする必要があります。

まず、 StandardSessionコンストラクターを見てみましょう。そのコードは次のとおりです。

 パブリックStandardSession(マネージャーマネージャー) {
 // デフォルトで呼び出されている Object クラスのコンストラクターを呼び出します // ここで再度宣言します。目的がわかりません。このクラスには以前に親クラスがあったのでしょうか?
 素晴らしい();
 
 this.manager = マネージャー;
 //アクセスカウントを有効にするかどうか if (ACTIVITY_CHECK) {
  accessCount = 新しい AtomicInteger();
 }
 }

StandardSessionを作成するときは、 Managerオブジェクトを渡してこのStandardSessionに関連付ける必要があります。これにより、 Managerに注意を向けることができます。 Managerとそのサブクラスの関係を次の図に示します。

ManagerBaseに注目すると、次のコードが見つかります。

保護された Map<String, Session> sessions = 新しい ConcurrentHashMap<>();

Sessionは Tomcat のカスタム インターフェイスです。StandardSession StandardSession HttpSessionおよびSessionインターフェイスを実装します。このインターフェイスには豊富な機能がありますが、プログラマーには提供されていません。

このプロパティを調べると、 Session に関連するすべての操作はsessionsを操作することによって実装されていることがわかります。したがって、 Session を保存するデータ構造はConcurrentHashMapであることが明確にわかります。

セッションを作成する方法

では、セッションはどのように作成されるのでしょうか?私は次のメソッドManagerBase.creaeSession見つけ、そのプロセスを次のようにまとめました。

  • セッション数が制限を超えているかどうかを確認し、超えている場合は例外をスローします。
  • StandardSession オブジェクトの作成
  • さまざまな必要なセッションプロパティ(有効性、最大タイムアウト、セッションID)を設定します。
  • SessionId を生成します。Tomcat はさまざまな SessionId アルゴリズムをサポートしています。私のデバッグ プロセスで使用した SessionId 生成アルゴリズムは LazySessionIdGenerator です (このアルゴリズムは、最初に乱数配列をロードするのではなく、使用時にロードするという点で他のアルゴリズムとは異なります。ここでのランダム配列は通常のランダム配列ではなく、SecureRandom です。関連情報については、ビッグ ガイの記事をお読みください)
  • セッション数を増やします。Tomcat の戦略は 100 セッションの作成率のみをカウントすることであるため、sessionCreationTiming は 100 の固定サイズのリンク リストです (先頭に null 値を持つ 100 個の要素)。したがって、リンク リストに新しいデータが追加された場合は、リンク リストから古いデータを削除して、固定サイズを確保する必要があります。セッション作成率を計算する式は次のとおりです。

(1000*60*カウンター)/(int)(現在 - 最古)

  • 今は統計データが取得された時刻です System.currentTimeMillis()
  • 最も古いセッションはキュー内で最も古いセッションが作成された時刻です
  • カウンタはキュー内の値がnullでない要素の数です
  • レートは 1 分ごとに計算されるため、ここでは 1000 に 60 を掛ける必要があります (1 分には 60,000 ミリ秒があります)。
 パブリックセッションcreateSession(String sessionId) {
 // セッションが制限を超えているかどうかを確認し、超えている場合は例外をスローします if ((maxActiveSessions >= 0) &&
  (getActiveSessions() >= maxActiveSessions)) {
  拒否されたセッション++;
  新しいTooManyActiveSessionsExceptionをスローします(
   sm.getString("managerBase.createSession.ise"),
   最大アクティブセッション数);
 }

 //このメソッドは StandardSession オブジェクトを作成します Session session = createEmptySession();

 //セッション内の必要な属性を初期化します。session.setNew(true);
 //セッションは利用可能ですか? session.setValid(true);
 //作成時間 session.setCreationTime(System.currentTimeMillis());
 //最大セッションタイムアウトを設定します。session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
 文字列 id = セッションID;
 id == nullの場合{
  id = セッションIDを生成する();
 }
 セッションIDを設定します。
 セッションカウンタ++;
 // セッションが作成された時刻を記録します。これは、セッションの作成率をカウントするために使用されます。 // 同様に、セッションの有効期限である ExpireRate があります。 // 他のスレッドが sessionCreationTiming を操作する可能性があるため、SessionTiming をロックする必要があります。timing = new SessionTiming(session.getCreationTime(), 0);
 同期 (セッション作成タイミング) {
  //sessionCreationTimingはLinkedListです
  //したがって、ポーリングはリンク リストの先頭にあるデータ、つまり最も古いデータを削除します sessionCreationTiming.add(timing);
  セッション作成タイミング.poll();
 }
 セッションを返す。
 }

セッションの破棄

Session を破棄するには、 ConcurrentHashMapから Session を削除する必要があります。手がかりをたどると、セッションを削除するためのコードは次のようになります。

 @オーバーライド
 パブリック void 削除(セッション セッション、ブール値 更新) {
 // 期限切れのセッション情報をカウントする必要があるかどうかを確認します if (update) {
  長いtimeNow = System.currentTimeMillis();
  整数タイムアライブ =
  (int) (timeNow - session.getCreationTimeInternal())/1000;
  セッションの最大アライブ時間を更新します(timeAlive);
  期限切れセッションを増加および取得します。
  SessionTiming タイミング = new SessionTiming(timeNow, timeAlive);
  同期 (セッション有効期限) {
  セッション有効期限を追加します。タイミングを設定します。
  セッション有効期限.poll();
  }
 }
 //マップからセッションを削除する if (session.getIdInternal() != null) {
  セッションを削除します。
 }
 }

破壊の時

積極的な破壊

HttpSession.invalidate()メソッドを呼び出すことでセッションの破棄を実行できます。このメソッドは最終的にStandardSession.invalidate()メソッドを呼び出します。コードは次のとおりです。 sessionを破棄する主なメソッドはStandardSession.expire()であることがわかります。

 パブリックボイド無効化() {

 if (!isValidInternal())
  新しい IllegalStateException をスローする
  (sm.getString("standardSession.invalidate.ise"));

 // このセッションを期限切れにする
 期限切れ();
 }

expireメソッドのコードは次のとおりです。

 @オーバーライド
 パブリックボイド有効期限(){

 有効期限が切れます(true);

 }
 パブリックvoid有効期限(ブール値通知) {
  //コードを省略//ConcurrentHashMap からセッションを削除します manager.remove(this, true);
  //省略されたコードは主に、セッションが破棄されることを各リスナーに通知するためのものです}

タイムアウト破壊

アクティブな破棄に加えて、セッションの有効期限を設定することもできます。有効期限が切れると、バックグラウンド スレッドによってセッションがアクティブに破棄されます。セッションの有効期限を短く設定し、 JConsoleを使用して呼び出しスタックを追跡し、どのオブジェクトとどのスレッドが破棄操作を実行したかを確認できます。

下の図に示すように、セッションに 30 秒のタイムアウトを設定します。

次にManagerBase.remove

メソッドにブレークポイントを設定し、以下に示すように30秒間待ちます。

Tomcat は、サブコンポーネントのbackgroundProcessメソッドを定期的に実行するためにバックグラウンド スレッドを開始します (サブコンポーネントが Tomcat によって管理され、 Managerインターフェイスを実装している場合)。

 @オーバーライド
 パブリック void backgroundProcess() {
 カウント = (カウント + 1) % プロセス有効期限頻度;
 (カウント == 0)の場合
  プロセス有効期限切れ();
 }

 パブリックボイドプロセス期限切れ() {

 長いtimeNow = System.currentTimeMillis();
 セッション sessions[] = findSessions();
 int 有効期限は 0 です。

 ログのデバッグが有効かどうか
  log.debug("" + getName() + " の期限切れセッションを " + timeNow + " sessioncount " + sessions.length に開始します);
 //JConsole の図から、isValid によって expire メソッドが呼び出される可能性があることがわかります。for (int i = 0; i < sessions.length; i++) {
  セッション[i]がnullの場合、セッション[i].isValid()がtrueになります。
  ここで期限切れ++;
  }
 }
 長いtimeEnd = System.currentTimeMillis();
 ログのデバッグが有効かどうか
  log.debug("期限切れセッションの終了 " + getName() + " processingTime " + (timeEnd - timeNow) + " 期限切れセッション: " + expireHere);
 処理時間 += ( 終了時間 - 現在時間 );

 }

Manager.backgroundProcessインターフェースのコメントを見てみましょう。簡単に訳すと、 backgroundProcessコンテナによって定期的に実行され、セッションのクリーンアップ タスクなどを実行するために使用できるということです。

 /**
 * このメソッドは、定期的にコンテキスト/コンテナによって呼び出されます。
 * ベースで、マネージャーが実装できるようにします
 * セッションの期限切れなどの定期的なタスクを実行するメソッド。
 */
 パブリック void backgroundProcess();

要約する

Session のデータ構造を下図に示します。簡単に言うと、 ConcurrentHashMap Session保存するために使用され、 Session ConcurrentHashMapを使用してキーと値のペアを保存します。その構造を下図に示します。 .jpg

つまり、セッションに個別のデータを追加する代わりに、以下に示すように個別のデータをオブジェクトにカプセル化するとパフォーマンスが向上します。

//悪い
httpSession.setAttribute("user","kesan");
httpSession.setAttribute("ニックネーム","いいね");
httpSession.setAttribute("sex","男");
....
//良い
ユーザー kesan = userDao.getUser()
httpSession.setAttribute("user", kesan);

セッションのリスナーを構成すると、セッションへの変更によってリスナー メソッドが現在のスレッドで直接実行されるため、リスナー内でブロックされる可能性のあるメソッドを実行しないことが最善です

Tomcat はバックグラウンド スレッドを開始し、定期的にManagerBase.backgroundProcessメソッドを実行して期限切れのセッションを検出し、破棄します。

思考の移行

オブジェクト生成率アルゴリズム このアルゴリズムの設計は非常に興味深く、他のプロジェクトにも適用できるため、次のようにまとめられます。

まず、固定サイズ (たとえば 100) のリンク リストが生成され、null 要素で埋められます。 新しいオブジェクトが作成されると、作成時刻がリンク リスト (もちろん、カプセル化されたオブジェクト) の末尾に追加され、リンク リストの先頭ノードが削除されます。このとき、削除されるオブジェクトは、null ノードか、リンク リストに最初に追加されたノードのいずれかです。オブジェクトの生成率を計算する場合は、リンク リスト内の null 以外の要素の数をカウントし、現在の時刻とオブジェクトが最初に作成された時刻の差で割って、率を取得します。 (時間単位の変換に注意してください)

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • Tomcatセッション管理分析についての簡単な説明
  • Nginx+Tomcat によるセッション管理の実装
  • Tomcatのセッション管理メカニズム
  • TomCatセッション管理の詳細な分析

<<:  MySQL 5.7 における基本的な JSON 操作ガイド

>>:  React 入門レベルの詳細なメモ

推薦する

CSS でフローティングにより親要素の高さが崩れる問題を解決するいくつかの方法

以前は、フロートはレイアウトによく使用されていましたが、フローティングレイアウトを使用すると親要素の...

Dockerはブリッジを追加し、IPアドレスの範囲を設定します

バイナリ docker 19.03 バージョンがインストール後に docker0 ブリッジを自動的に...

MySQL 8.0.18はデータベースにユーザーを追加し、権限を付与します

1. データベースにログインするには、rootユーザーを使用することをお勧めします。 mysql -...

Centos8 に nginx1.9.1 をインストールする詳細な手順

1.17.9 本当はもっと美味しいNginx のダウンロード アドレス: https://nginx...

Linux システムで複数のバージョンの PHP を共存させるソリューション (超シンプル)

PHP7が出たので、最新バージョンのファンとしては、早速アップグレードして体験してみました。しかし...

CSS カウンターとコンテンツの概要

コンテンツ プロパティは CSS 2.1 で導入され、:before および :after 疑似要素...

docker view container log コマンドの実装

なぜログを読む必要があるのでしょうか?たとえば、コンテナの起動に失敗したがプロンプトが表示されない場...

Dockerを使用してDjango+MySQL8開発環境をデプロイする方法の詳細な説明

しばらく前にシステムを再インストールしましたが、バックアップを取っていなかったので、コンピューター上...

CSSテキストシャドウの徐々にぼやける効果の実装

テキストシャドウテキストに影を追加します。テキストとテキスト装飾に複数のシャドウを追加することができ...

Vue 仮想 DOM の問題について

目次1. 仮想DOMとは何ですか? 2. 仮想 DOM が必要な理由3. 仮想DOMはどのようにして...

jsonファイルの書き方の詳細説明

目次JSONとはなぜこの技術なのでしょうか? JSONの使い方- データ形式- メモ- JSには2つ...

MySQL の主キーがクエリを高速化するために数値を使用するか UUID を使用するかについての簡単な分析

実際の開発では、MySQL の主キーは重複できず、主キーが自動的にインクリメントされることがあります...

CSS3は水平方向の中央揃え、垂直方向の中央揃え、水平方向と垂直方向の中央揃えのサンプルコードを実装しています。

フロントエンドの担当者であれば、面接でも仕事中でも、「CSS を使用して中央揃えにする」という効果に...

親子コンポーネントの通信を解決するための3つのVueスロット

目次序文環境の準備カテゴリコンポーネントアプリのコンポーネント1. デフォルトスロット2. 名前付き...

MySQL PXC は IST 送信のみで新しいノードを構築します (推奨)

需要シナリオ: 既存の PXC 環境には大量のデータがあります。新しく購入したサーバーをこのクラスタ...