Ein Beispiel, wie Tomcat Session verwaltet

Ein Beispiel, wie Tomcat Session verwaltet

Sie haben ConcurrentHashMap gelernt, wissen aber nicht, wie Sie es anwenden sollen? Ich verwende die Sitzung von Tomcat, weiß aber nicht, wie sie implementiert ist. Wie wird die Sitzung erstellt und gelöscht? Lesen Sie einfach weiter und Sie werden es herausfinden.

Sitzungsstruktur

Und nun ohne weitere Umschweife die Bilder

Wenn wir die obige Abbildung genauer betrachten, können wir folgende Schlussfolgerungen ziehen

  • HttpSession ist die Schnittstellenklasse für den Betrieb einer Session im JavaEE-Standard, daher betreiben wir tatsächlich die Klasse StandardSessionFacade
  • Die von Session zum Speichern von Daten verwendete Datenstruktur ist ConcurrentHashMap . Wie Sie in der Abbildung sehen können, haben wir eine Nachricht in Session gespeichert.

Warum müssen wir ConcurrentHashMap verwenden? Der Grund dafür ist, dass bei der Verarbeitung einer HTTP-Anforderung nicht nur ein Thread auf die Sitzung zugreift. Moderne Webanwendungen müssen beim Zugriff auf eine Seite normalerweise mehrere Anforderungen gleichzeitig ausführen, und diese Anforderungen können gleichzeitig von verschiedenen Threads im Webcontainer ausgeführt werden. Wenn HashMap verwendet wird, können daher leicht Thread-Sicherheitsprobleme auftreten.

Schauen wir uns zunächst die Wrapper-Klasse HttpSession an.

Standardsitzungsfassade

In diesem Kurs können wir die praktische Anwendung des Erscheinungsmusters (Facde) erlernen. Die Definition finden Sie weiter unten.

öffentliche Klasse StandardSessionFacade implementiert HttpSession

Wie implementiert diese Klasse also die Session-Funktion? Aus dem folgenden Code ist nicht schwer zu erkennen, dass diese Klasse keine echte Implementierungsklasse von HttpSession ist, sondern ein Wrapper der echten HttpSession-Implementierungsklasse, der nur die Methoden in der HttpSession-Schnittstelle verfügbar macht, was im Entwurfsmuster der Fassadenmodus ist.

 private letzte HttpSession-Sitzung;
 öffentliche StandardSessionFacade(HttpSession-Sitzung) {
 diese.sitzung = Sitzung;
 }

Warum verwenden wir also nicht einfach die Implementierungsklasse HttpSession?

Aus Abbildung 1 können wir erkennen, dass die tatsächliche Implementierungsklasse von HttpSession StandardSession ist. Angenommen, in dieser Klasse sind einige Methoden definiert, die von Tomcat und nicht vom Programm aufgerufen werden sollen, dann können wir aufgrund des Java-Typsystems direkt mit dieser Klasse arbeiten, was einige unvorhergesehene Probleme mit sich bringt, wie im folgenden Code gezeigt.

Wenn wir StandardSession mit einer weiteren Ebene umschließen, tritt bei der Ausführung des obigen Codes ein Fehler auf. Wie in der folgenden Abbildung gezeigt, wird eine Ausnahme bei der Typkonvertierung ausgelöst, wodurch ungültige Vorgänge hier verhindert werden.

Können wir im weiteren Verlauf direkt auf StandardSession zugreifen, ohne die Fassadenklasse zu durchlaufen?

Tatsächlich ist es möglich. Wir können StandardSession über einen Reflexionsmechanismus erhalten, aber Sie sollten besser wissen, was Sie tun. Der Code lautet wie folgt

 @GetMapping("/s")
 öffentlicher String sessionTest(HttpSession httpSession) wirft ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
 StandardSessionFacade-Sitzung = (StandardSessionFacade) httpSession;
 Klasse Zielklasse = Klasse.fürName(session.getClass().getName());

 //Sichtbarkeit ändern Feld standardSessionField = targetClass.getDeclaredField("session");
 standardSessionField.setAccessible(true);
 //StandardSession abrufen standardSession = (StandardSession) standardSessionField.get(session);
 
 return standardSession.getManager().toString();
 }

Standardsitzung

Die Definition dieser Klasse lautet wie folgt

öffentliche Klasse StandardSession implementiert 
HttpSession, Sitzung, Serialisierbar

Anhand ihrer Schnittstelle können wir erkennen, dass diese Klasse zusätzlich zu den von HttpSession im JavaEE-Standard geforderten Funktionen auch über Serialisierungsfunktionen verfügt.

In Abbildung 1 wissen wir bereits, dass StandardSession ConcurrentHashMap zum Speichern von Daten verwendet. Daher konzentrieren wir uns als Nächstes auf die Implementierung der Serialisierung und Deserialisierung von StandardSession sowie auf die Funktion des Listeners.

Serialisierung

Erinnern Sie sich, dass wir im letzten Abschnitt StandardSession durch den Reflexionsmechanismus erhalten haben? Mithilfe des folgenden Codes können wir direkt beobachten, wie die deserialisierte StandardSession aussieht.

 @GetMapping("/s")
 public void sessionTest(HttpSession httpSession, HttpServletResponse response) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IOException {
 StandardSessionFacade-Sitzung = (StandardSessionFacade) httpSession;
 Klasse Zielklasse = Klasse.fürName(session.getClass().getName());

 //Sichtbarkeit ändern Feld standardSessionField = targetClass.getDeclaredField("session");
 standardSessionField.setAccessible(true);
 //StandardSession abrufen standardSession = (StandardSession) standardSessionField.get(session);
 
 //Einige Daten zur Beobachtung speichernstandardSession.setAttribute("msg","hallo, Welt");
 standardSession.setAttribute("Benutzer","Benutzername");
 standardSession.setAttribute("Passwort", "Gefällt mir");
 standardSession.setAttribute("tel", 10086L);
 //Schreibe das serialisierte Ergebnis direkt in die HTTP-Antwort ObjectOutputStream objectOutputStream = new ObjectOutputStream(response.getOutputStream());
 
 Standardsitzung.writeObjectData(objectOutputStream);
 }

Wenn nichts schief geht, führt der Browser, der auf diese Schnittstelle zugreift, den Downloadvorgang durch und erhält schließlich eine Datei

Verwenden Sie WinHex zum Öffnen und Analysieren. Die Abbildung zeigt das Ergebnis nach der Serialisierung, das hauptsächlich aus vielen Trennzeichen sowie Typinformationen und Werten besteht, wie beispielsweise den Standardinformationen im roten Feld in der Abbildung.

Es ist nicht empfehlenswert, zu untersuchen, wie serialisierte Dateien Daten organisieren, da dies nicht sehr aussagekräftig ist.

Wenn Sie wirklich interessiert sind, empfehlen wir Ihnen, den folgenden Code zu lesen: org.apache.catalina.session.StandardSession.doWriteObject

Zuhörer

Im JavaEE-Standard können wir Sitzungsänderungen überwachen, indem wir HttpSessionAttributeListener konfigurieren. Wie wird es also in StandardSession implementiert? Wenn Sie das Beobachtermuster verstehen, kennen Sie die Antwort wahrscheinlich bereits. Am Beispiel von setAttribute wird nach dem Aufruf dieser Methode sofort die Listener-Methode in diesem Thread zur Verarbeitung aufgerufen, was bedeutet, dass wir keine Vorgänge ausführen sollten, die den Listener zu lange blockieren.

 public void setAttribute(Stringname, Objektwert, boolean benachrichtigen) {
 //Irrelevanten Code weglassen//Den oben konfigurierten Ereignis-Listener abrufenObject listeners[] = context.getApplicationEventListeners();
 wenn (Listeners == null) {
  zurückkehren;
 }
 für (int i = 0; i < listeners.length; i++) {
  //Nur HttpSessionAttributeListener kann ausgeführt werden, wenn (!(listeners[i] instanceof HttpSessionAttributeListener)) {
  weitermachen;
  }
  HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners[i];
  versuchen {
  //Rufen Sie die Verarbeitungsmethode des Listeners im aktuellen Thread auf, wenn (ungebunden != null) {
   if (ungebunden != Wert || manager.getNotifyAttributeListenerOnUnchangedValue()) {
   //Wenn der Wert eines Schlüssels geändert wird, rufen Sie die Methode „attributeReplaced“ des Listeners auf. context.fireContainerEvent("beforeSessionAttributeReplaced", listener);
   wenn (Ereignis == null) {
    Ereignis = neues HttpSessionBindingEvent(getSession(), Name, ungebunden);
   }
   listener.attributeErsetzt(Ereignis);
   Kontext.fireContainerEvent("afterSessionAttributeReplaced", Listener);
   }
  } anders {
   //Wenn ein neuer Schlüssel hinzugefügt wird, führen Sie die Methode attributeAdded aus. context.fireContainerEvent("beforeSessionAttributeAdded", listener);
   wenn (Ereignis == null) {
   Ereignis = neues HttpSessionBindingEvent(getSession(), Name, Wert);
   }
   listener.attributeAdded(Ereignis);
   Kontext.fireContainerEvent("afterSessionAttributeAdded", Listener);
  }
  } fangen (Wurfbares t) {
  //Ausnahmebehandlung}
 }
 }

Lebenszyklus einer Sitzung

So speichern Sie die Sitzung

Nachdem wir den Aufbau einer Sitzung verstanden haben, gilt es zu klären, wann StandardSession erstellt wird und welche Punkte dabei zu beachten sind.

Schauen wir uns zunächst den StandardSession Konstruktor an. Sein Code lautet wie folgt.

 öffentliche StandardSession(Manager manager) {
 //Rufen Sie den Konstruktor der Object-Klasse auf, der standardmäßig aufgerufen wurde. //Deklarieren Sie ihn hier noch einmal. Ich kenne seinen Zweck nicht. Vielleicht hat diese Klasse bereits eine übergeordnete Klasse?
 super();
 
 dieser.manager = Manager;
 //Ob der Zugriffszähler aktiviert werden soll, wenn (ACTIVITY_CHECK) {
  Zugriffsanzahl = neue AtomicInteger();
 }
 }

Beim Erstellen StandardSession müssen Sie ein Manager Objekt übergeben, um es mit dieser StandardSession zu verknüpfen, damit wir uns Manager zuwenden können. Die Beziehung zwischen Manager und seinen Unterklassen wird in der folgenden Abbildung dargestellt.

Wir wenden unsere Aufmerksamkeit ManagerBase zu und finden den folgenden Code.

geschützte Map<String, Session> Sitzungen = neue ConcurrentHashMap<>();

Session ist eine benutzerdefinierte Schnittstelle von Tomcat. StandardSession implementiert die Schnittstellen HttpSession und Session . Diese Schnittstelle verfügt über umfangreichere Funktionen, wird aber Programmierern nicht zur Verfügung gestellt.

Durch Nachschlagen dieser Eigenschaft können wir feststellen, dass alle mit der Sitzung in Zusammenhang stehenden Vorgänge durch Ausführen sessions implementiert werden. Daher können wir deutlich erkennen, dass die Datenstruktur, die die Sitzung speichert, ConcurrentHashMap ist.

So erstellen Sie eine Sitzung

Wie wird also die Sitzung erstellt? Ich habe die folgende Methode ManagerBase.creaeSession gefunden und ihren Ablauf wie folgt zusammengefasst.

  • Überprüfen Sie, ob die Anzahl der Sitzungen den Grenzwert überschreitet, und lösen Sie ggf. eine Ausnahme aus.
  • Erstellen eines StandardSession-Objekts
  • Festlegen verschiedener erforderlicher Sitzungseigenschaften (Gültigkeit, maximales Timeout, Sitzungs-ID)
  • SessionId generieren. Tomcat unterstützt verschiedene SessionId-Algorithmen. Der in meinem Debugging-Prozess verwendete Algorithmus zur SessionId-Generierung ist LazySessionIdGenerator (dieser Algorithmus unterscheidet sich von anderen Algorithmen dadurch, dass er das Zufallszahlen-Array nicht zu Beginn lädt, sondern erst, wenn es verwendet wird. Das Zufalls-Array hier ist kein gewöhnliches Zufalls-Array, sondern SecureRandom. Weitere Informationen hierzu finden Sie im Artikel des großen Kerls.)
  • Erhöhen Sie die Sitzungsanzahl. Da die Strategie von Tomcat darin besteht, nur die Erstellungsrate von 100 Sitzungen zu zählen, ist sessionCreationTiming eine verknüpfte Liste mit einer festen Größe von 100 (100 Elemente mit Nullwerten am Anfang). Wenn der verknüpften Liste neue Daten hinzugefügt werden, müssen daher die alten Daten aus der verknüpften Liste entfernt werden, um ihre feste Größe sicherzustellen. Die Formel zur Berechnung der Sitzungserstellungsrate lautet wie folgt

(1000*60*Zähler)/(int)(jetzt – ältester)
In

  • jetzt ist der Zeitpunkt, zu dem die statistischen Daten abgerufen werden System.currentTimeMillis()
  • älteste ist der Zeitpunkt, zu dem die älteste Sitzung in der Warteschlange erstellt wurde
  • Zähler ist die Anzahl der Elemente in der Warteschlange, deren Wert nicht null ist
  • Da die Rate pro Minute berechnet wird, müssen wir hier 1000 mit 60 multiplizieren (eine Minute hat 60.000 Millisekunden).
 öffentliche Sitzung erstellenSession(String sessionId) {
 // Überprüfen Sie, ob die Sitzung das Limit überschreitet, und werfen Sie in diesem Fall eine Ausnahme, wenn ((maxActiveSessions >= 0) &&
  (getActiveSessions() >= maxActiveSessions)) {
  abgelehnte Sitzungen++;
  neue TooManyActiveSessionsException werfen(
   sm.getString("managerBase.createSession.ise"),
   maxActiveSessions);
 }

 //Diese Methode erstellt ein StandardSession-Objekt Session session = createEmptySession();

 //Initialisiere die erforderlichen Attribute in der Sitzung session.setNew(true);
 //Ist die Sitzung verfügbar? session.setValid(true);
 //Erstellungszeit session.setCreationTime(System.currentTimeMillis());
 //Maximales Sitzungstimeout festlegen session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
 String-ID = Sitzungs-ID;
 wenn (id == null) {
  id = generateSessionId();
 }
 Sitzung.setId(id);
 Sitzungszähler++;
 //Zeichne den Zeitpunkt der Sitzungserstellung auf. Dieser wird zum Zählen der Erstellungsrate der Sitzung verwendet. //Ebenso gibt es ExpireRate, die Ablaufrate der Sitzung. //Da andere Threads möglicherweise mit sessionCreationTiming arbeiten, muss SessionTiming gesperrt werden timing = new SessionTiming(session.getCreationTime(), 0);
 synchronisiert (sessionCreationTiming) {
  //sessionCreationTiming ist LinkedList
  //Daher entfernt die Umfrage die Daten am Anfang der verknüpften Liste, also die ältesten Daten sessionCreationTiming.add(timing);
  Sitzungserstellungstiming.poll();
 }
 Sitzung zurückgeben;
 }

Zerstören einer Sitzung

Um die Sitzung zu zerstören, muss sie aus ConcurrentHashMap entfernt werden. Wenn wir den Hinweisen folgen, können wir feststellen, dass der Code zum Entfernen der Sitzung wie folgt lautet.

 @Überschreiben
 public void entfernen(Sitzung Sitzung, boolean update) {
 //Überprüfen Sie, ob es notwendig ist, die Informationen zu abgelaufenen Sitzungen zu zählen, wenn (Update) {
  lange ZeitJetzt = System.currentTimeMillis();
  int timeAlive =
  (int) (ZeitJetzt - Sitzung.getCreationTimeInternal())/1000;
  Aktualisieren Sie SessionMaxAliveTime(timeAlive);
  abgelaufeneSitzungen.incrementAndGet();
  SessionTiming-Zeitgebung = neues SessionTiming (aktuelle Zeit, Lebenszeit);
  synchronisiert (sessionExpirationTiming) {
  SitzungsablaufTiming.add(Zeitpunkt);
  Sitzungsablauftiming.poll();
  }
 }
 //Sitzung aus Map entfernen if (session.getIdInternal() != null) {
  Sitzungen.entfernen(session.getIdInternal());
 }
 }

Zeit der Zerstörung

Aktive Zerstörung

Wir können die Sitzung zerstören, indem HttpSession.invalidate() aufrufen. Diese Methode ruft letztendlich StandardSession.invalidate() auf. Der Code lautet wie folgt. Es ist ersichtlich, dass die Schlüsselmethode zum Zerstören session StandardSession.expire() ist

 öffentliche Leere ungültig machen() {

 wenn (!isValidInternal())
  neue IllegalStateException werfen
  (sm.getString("standardSession.invalidate.ise"));

 // Diese Sitzung ablaufen lassen
 erlöschen();
 }

Der Code der expire -Methode lautet wie folgt

 @Überschreiben
 öffentliche Leere abgelaufen () {

 ablaufen(wahr);

 }
 öffentliche Leere abgelaufen (Boolesche Benachrichtigung) {
  //Code auslassen//Sitzung aus ConcurrentHashMap entfernen manager.remove(this, true);
  //Der ausgelassene Code dient hauptsächlich dazu, jeden Listener über die Zerstörung der Sitzung zu benachrichtigen}

Timeout-Zerstörung

Zusätzlich zur aktiven Zerstörung können wir eine Ablaufzeit für die Sitzung festlegen. Wenn die Zeit erreicht ist, wird die Sitzung vom Hintergrundthread aktiv zerstört. Wir können eine kürzere Ablaufzeit für die Sitzung festlegen und dann JConsole verwenden, um ihren Aufrufstapel zu verfolgen und zu sehen, welches Objekt und welcher Thread den Zerstörungsvorgang ausgeführt hat.

Wie in der Abbildung unten gezeigt, legen wir ein Timeout von 30 Sekunden für die Sitzung fest.

Dann ManagerBase.remove

Setzen Sie einen Haltepunkt auf die Methode und warten Sie 30 Sekunden, wie unten gezeigt

Tomcat startet einen Hintergrundthread, um die Methode backgroundProcess der Unterkomponente regelmäßig auszuführen (vorausgesetzt, die Unterkomponente wird von Tomcat verwaltet und implementiert die Manager Schnittstelle).

 @Überschreiben
 public void Hintergrundprozess() {
 Anzahl = (Anzahl + 1) % processExpiresFrequency;
 wenn (Anzahl == 0)
  Prozess läuft ab();
 }

 public void Prozess läuft ab() {

 lange ZeitJetzt = System.currentTimeMillis();
 Sitzung Sitzungen[] = findSessions();
 int expireHere = 0;

 wenn(log.isDebugEnabled())
  log.debug("Starten Sie ablaufende Sitzungen " + getName() + " um " + timeNow + " sessioncount " + sessions.length);
 //Aus dem JConsole-Diagramm können wir ersehen, dass isValid dazu führen kann, dass die Methode expire aufgerufen wird für (int i = 0; i < session.length; i++) {
  wenn (sessions[i]!=null und !sessions[i].isValid()) {
  läuft hier ab++;
  }
 }
 langes timeEnd = System.currentTimeMillis();
 wenn(log.isDebugEnabled())
  log.debug("Ende abgelaufener Sitzungen " + getName() + " processingTime " + (timeEnd - timeNow) + " abgelaufene Sitzungen: " + expireHere);
 Verarbeitungszeit += (Endzeit – Jetztzeit);

 }

Wir können uns die Kommentare in der Schnittstelle Manager.backgroundProcess ansehen. Kurz gesagt wird backgroundProcess regelmäßig vom Container ausgeführt und kann zum Ausführen von Sitzungsbereinigungsaufgaben usw. verwendet werden.

 /**
 * Diese Methode wird vom Kontext/Container in regelmäßigen Abständen aufgerufen
 * Basis und ermöglicht dem Manager die Umsetzung
 * eine Methode, die periodische Aufgaben ausführt, wie z. B. das Ablaufen von Sitzungen usw.
 */
 öffentliche void Hintergrundprozess();

Zusammenfassen

Die Datenstruktur von Session ist in der folgenden Abbildung dargestellt. Einfach ausgedrückt wird ConcurrentHashMap zum Speichern von Session verwendet, und Session verwendet ConcurrentHashMap zum Speichern von Schlüssel-Wert-Paaren. Die Struktur ist in der folgenden Abbildung dargestellt. .jpg

Dies bedeutet, dass es leistungsfähiger ist, die diskreten Daten in einem Objekt zu kapseln, anstatt der Sitzung diskrete Daten hinzuzufügen, wie unten gezeigt.

//schlecht
httpSession.setAttribute("Benutzer","Benutzername");
httpSession.setAttribute("Spitzname","Gefällt mir");
httpSession.setAttribute("Geschlecht", "Geschlecht");
....
//Gut
Benutzername = userDao.getUser()
httpSession.setAttribute("Benutzer", nein);

Wenn Sie einen Listener für die Sitzung konfigurieren, wird bei allen Änderungen an der Sitzung die Listener-Methode direkt im aktuellen Thread ausgeführt. Daher sollten Sie am besten keine Methoden ausführen, die den Listener blockieren könnten .

Tomcat startet einen Hintergrund-Thread, um regelmäßig ManagerBase.backgroundProcess auszuführen, um abgelaufene Sitzungen zu erkennen und zu zerstören.

Gedankenmigration

Algorithmus zur Objektgenerierungsrate Dieser Algorithmusentwurf ist recht interessant und kann auch auf andere Projekte angewendet werden, daher folgt die folgende Zusammenfassung.

Zuerst wird eine verknüpfte Liste fester Größe (sagen wir 100) generiert und dann mit Nullelementen gefüllt. Wenn ein neues Objekt erstellt wird, wird die Erstellungszeit am Ende der verknüpften Liste hinzugefügt (natürlich ist es das gekapselte Objekt) und dann wird der Kopfknoten der verknüpften Liste entfernt. Zu diesem Zeitpunkt ist das entfernte Objekt entweder ein Nullknoten oder der Knoten, der zuerst zur verknüpften Liste hinzugefügt wurde. Wenn Sie die Objektgenerierungsrate berechnen möchten, zählen Sie die Anzahl der nicht-null-Elemente in der verknüpften Liste und dividieren Sie sie durch die Differenz zwischen der aktuellen Zeit und der Zeit, zu der das Objekt erstmals erstellt wurde, um die Rate zu erhalten. (Beachten Sie die Umrechnung der Zeiteinheiten)

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, er wird für jedermanns Studium hilfreich sein. Ich hoffe auch, dass jeder 123WORDPRESS.COM unterstützen wird.

Das könnte Sie auch interessieren:
  • Eine kurze Diskussion zur Tomcat-Sitzungsverwaltungsanalyse
  • Implementierung der Sitzungsverwaltung mit Nginx+Tomcat
  • Sitzungsverwaltungsmechanismus in Tomcat
  • Detaillierte Analyse der TomCat-Sitzungsverwaltung

<<:  Grundlegender JSON-Betriebsleitfaden in MySQL 5.7

>>:  Detaillierte Hinweise zu React für Einsteiger

Artikel empfehlen

Bei der anonymen Mysql-Anmeldung kann keine Datenbankproblemlösung erstellt werden

Häufig gestellte Fragen Der Zugriff für den Benut...

Beispiel für die MySQL-Methode zum Löschen von Daten und Datentabellen

Es ist sehr einfach, Daten und Tabellen in MySQL ...

So verwenden Sie Spark und Scala zum Analysieren von Apache-Zugriffsprotokollen

Installieren Zuerst müssen Sie Java und Scala ins...

js implementiert Tabellen-Drag-Optionen

In diesem Artikelbeispiel wird der spezifische JS...

Vue+SSH-Framework zur Realisierung von Online-Chat

In diesem Artikel wird der spezifische Code des V...

Analysieren Sie den Unterschied zwischen ES5 und ES6

Inhaltsverzeichnis Überblick Funktionssignatur Op...

Delegieren von Berechtigungen in Linux mit Sudo

Einführung in die Sudo-Autoritätsdelegierung su-S...

Ausführliche Erklärung zum Currying von JS-Funktionen

Inhaltsverzeichnis 1. Ergänzende Wissenspunkte: i...