Lösung für das lange laufende Abfrageproblem in einer Webanwendung (asynchrone Anfrage)

8

Hier ist das Problem

Ein Benutzer einer Enterprise-Webanwendung führt eine Aufgabe aus, die zu einer langen (sehr langen) Datenbankabfrage (oder einer anderen langen, die Verarbeitung intensiven Task) führt

Probleme:

  • Request Timeout - nach einer Weile kann der Benutzer eine Anfrage Timeout
  • bekommen
  • Sitzungs-Timeout - Wenn keine Sitzungshaltungsmethoden verwendet werden, kann ein Sitzungs-Timeout auftreten
  • Thread-Sperre anfordern
    • Da der Anforderungsthread nicht zurückkehrt, blockiert er möglicherweise neue Anforderungen (wenn das Poollimit erreicht wird)
    • Auf einigen Anwendungsservern kann der Status des Serverzustands einen erzwungenen Neustart des Knotens oder der Anwendung auslösen (aufgrund eines lange laufenden Anfragethreads)
  • Wenn der Benutzer die Seite verlässt:
    • Die Transaktion wird nicht abgebrochen - was zu einer nutzlosen Verarbeitung führt, von der niemand profitieren wird
    • Der Benutzer kann nicht zurückkehren, um die Ergebnisse zu sehen, nachdem er
    • abgeschlossen hat
  • keine Fortschrittsanzeige - der Benutzer wartet nur darauf, dass die Seite aktualisiert wird

Es gibt mehrere Lösungen, die ich entwickelt habe, aber ich bin mir nicht sicher, ob ich weiß, was besser ist (in allen Aspekten, Leistung, Best Practice, Eleganz und Wartbarkeit) und ich würde gerne wissen, was Ihre empfohlene Lösung ist Wenn es eine Lösung gibt, die ich verpasst habe? (wahrscheinlich ja, und viele)

Die schlechte Lösung : Verwenden Sie den Anforderungs-Thread als Arbeitsthread, speichern Sie den Fortschrittsstatus in der Sitzung, lassen Sie einen AJAX-Aufruf den Status (in der Sitzung) in einer anderen parallelen Anforderung überprüfen

Die Kompromisslösung : Erstellen Sie Ihren eigenen Thread-Pool, behandeln Sie einen Überwachungsthread und einen Arbeitsthread und kümmern Sie sich um das Clustering, indem Sie die Status in einem verteilten Transaktions-Cache oder persistenten Speicher synchronisieren. Dadurch wird die Anforderung freigegeben, es werden jedoch Threads erstellt, die dem Anwendungsserver nicht bekannt sind, und sie werden nicht in Deimplementierung geschlossen. Es liegt an Ihnen, Threads sauber zu beenden, und es besteht immer die Möglichkeit, dass Sie etwas verlieren. Dies ist nicht der J2EE-Weg, es zu tun.

Die J2EE-Lösung : Verwenden Sie JMS für die asynchrone Task, das ist es für

die Spring-Lösung : Verwenden Sie Spring batch

Was würden Sie in Ihren Projekten tun / tun? Welche anderen Lösungen kennen Sie? Welche von denen, die ich oben erwähnt habe, ist Ihrer Meinung nach der Gewinner?

    
Eran Medan 30.12.2009, 09:32
quelle

4 Antworten

13

Ich würde eine Kombination aus Ihrer sogenannten "schlechten Lösung" und der "j2ee-Lösung" machen:

  1. Der ursprüngliche UI-Thread sendet eine asynchrone JMS-Nachricht an das "Backend" und gibt
  2. zurück
  3. Das Backend empfängt die asynchrone Anfrage und verarbeitet sie.
  4. Wenn ein Ergebnis (oder Fehler) im Backend erreicht wird, wird es an die Controller-Schicht zurückgegeben.
  5. Die Benutzeroberfläche, die gerade AJAX-Abfragen durchführt oder Bayeux / Cometed verwendet, empfängt und zeigt das Ergebnis an.

Der Trick besteht darin, das Anfrage / Antwort-Paar zu vergleichen. Ich würde es so machen:

  1. Erstellen Sie einen "AsyncManagerService" (AMS) mit SESSION, vielleicht sogar APPLICATION, so dass alle Threads mit derselben Instanz kommunizieren.
  2. Das AMS enthält eine Karte mit einer ID als Schlüssel und einem beliebigen Objekt als Wert.
  3. Erstellen Sie beim Erstellen der JMS-Nachrichtenanforderung einen eindeutigen Zufallsschlüssel und fügen Sie ihn in die jmsCorrelationId der Nachricht sowie in die Karte ein, wobei NULL als Wert verwendet wird. Übergeben Sie diese ID auch an die Benutzeroberfläche.
  4. Lassen Sie den UI-Thread das AMS mit der zuvor generierten ID abfragen, solange der Wert in der Karte NULL ist.
  5. Wenn das Ergebnis fertig ist, lassen Sie es von Ihrem JMS-Empfänger unter der angegebenen ID in die AMS-Karte einfügen.
  6. Wenn der UI-Thread das nächste Mal die Karte abfragt, erhält er die Antwort und stoppt die Abfrage.

Dies ist sauber und gut abstrahiert von jeder konkreten Domäne . Eine rein technische Lösung.

Auch wenn Sie das Polling nicht mögen, ist HTTP statuslos und ich denke, dass die Abfrage nur in genau definierten Intervallen erfolgt.

Wie auch immer, ich habe ein System mit genau diesem Muster implementiert und es läuft gut ...

    
raoulsson 30.12.2009, 10:44
quelle
1

Die Lösung, die ich vorher benutzt habe, beinhaltet Jetty Cometd anstelle von AJAX. Der Hauptunterschied zwischen Ajax und Cometd ist, dass Cometd mehr von einem Pub / Sub-Modell verwenden kann - der Client (Webbrowser in diesem Fall) abonniert den Publisher (Ihre App) und die App schiebt Updates und Benachrichtigungen an den Webbrowser als an ein Ajax-Modell der konstanten Abfrage des Servers durch den Web-Browser. Sie können die Cometd-Lösung auch dann nutzen, wenn Sie keinen Jetty verwenden - Sie können die Jetty-Jars in den lib-Ordner Ihres jeweiligen Web-Servers legen und es sollte Ihnen gut gehen.

    
trebor 30.12.2009 09:58
quelle
1

Zeigen Sie dem Benutzer eine Nachricht, dass "Ihre Anfrage akzeptiert wird und es eine Stunde dauern wird, bis sie aktualisiert wird"

Sie erstellen eine Tabelle, die alle diese Transaktionen speichert und diese in einem Stapel auf dem Server verarbeitet.

Der Benutzer muss nicht lange warten und er wird sich freuen, die Nachricht zu sehen. Sie können eine Bestätigungs-E-Mail senden, sobald Ihre Transaktion verarbeitet wurde.

Dies ist die beste Lösung, denke ich.

    
Ravia 30.12.2009 12:10
quelle
0

Wie lange laufen diese Abfragen?

Wenn Sie davon sprechen, dass Sitzungen auslaufen, ist es vielleicht am besten, wenn der Benutzer nicht darauf wartet. Es wird immer lästig sein, wenn der Benutzer 20 Minuten lang auf dem Tab der Abfrage warten muss.

Also, wenn wirklich lange Abfragen der Fall sind, ist es vielleicht am besten, den UI-Ansatz zu ändern und den Benutzer dazu zu bringen, eine Abfrage zu "bestellen", die er später wieder ansieht oder die sogar per Mail benachrichtigt wird .

In einem solchen Fall würde ich die Abfrage in der Datenbank vorbereiten und in einer separaten Tabelle zwischenspeichern. Das heißt, ich würde den Webserver einmal arbeiten lassen, um die Anfrage zu registrieren, eine DB-Aufgabe oder einen separaten Prozess / Server auf Anfragen vorbereiten und den Benutzer benachrichtigen, und dann den Webserver anzeigen lassen, wenn der Benutzer für die zurückkommt Ergebnisse.

    
Asaf R 30.12.2009 10:17
quelle

Tags und Links