Unterstützung für asynchrone Verarbeitung in Servlet 3.0

Selbst als API auf mittlerer Ebene, die in modernen, auf UI-Komponenten basierenden Webframeworks und Webdiensttechnologien integriert ist, wird die eingehende Servlet 3.0-Spezifikation (JSR 315) bahnbrechende Auswirkungen auf die Entwicklung von Java-Webanwendungen haben. Der Autor Xinyu Liu erklärt ausführlich, warum die asynchrone Verarbeitung für die kollaborativen Mehrbenutzeranwendungen, die Web 2.0 definieren, von grundlegender Bedeutung ist. Er fasst auch die anderen Verbesserungen von Servlet 3.0 zusammen, wie z. B. einfache Konfiguration und Steckbarkeit. Level: Mittelstufe

Die Java-Servlet-Spezifikation ist der gemeinsame Nenner für die meisten serverseitigen Java-Webtechnologien, einschließlich JavaServer Pages (JSP), JavaServer Faces (JSF), zahlreiche Webframeworks, SOAP- und RESTful-Webdienst-APIs sowie Newsfeeds. Die Servlets, die unter diesen Technologien ausgeführt werden, machen sie auf alle Java-Webserver (Servlet-Container) portierbar. Alle vorgeschlagenen Änderungen an dieser allgemein akzeptierten API für die Verarbeitung von HTTP-Kommunikation wirken sich möglicherweise auf alle verbundenen serverseitigen Webtechnologien aus.

Die bevorstehende Servlet 3.0-Spezifikation, die im Januar 2009 öffentlich überprüft wurde, ist eine Hauptversion mit wichtigen neuen Funktionen, die das Leben von Java-Webentwicklern zum Besseren verändern werden. Hier ist eine Liste dessen, was Sie in Servlet 3.0 erwarten können:

  • Asynchrone Unterstützung
  • Einfache Konfiguration
  • Steckbarkeit
  • Verbesserungen an vorhandenen APIs

Die asynchrone Unterstützung ist die wichtigste Erweiterung von Servlet 3.0, mit der die serverseitige Verarbeitung von Ajax-Anwendungen wesentlich effizienter gestaltet werden soll. In diesem Artikel werde ich mich auf die asynchrone Unterstützung in Servlet 3.0 konzentrieren und zunächst die Verbindungs- und Thread-Verbrauchsprobleme erläutern, die der Notwendigkeit einer asynchronen Unterstützung zugrunde liegen. Ich werde dann erklären, wie reale Anwendungen heutzutage die asynchrone Verarbeitung in Server-Push-Implementierungen wie Comet oder Reverse Ajax verwenden. Abschließend möchte ich auf die anderen Verbesserungen von Servlet 3.0 eingehen, z. B. die Steckbarkeit und die einfache Konfiguration, damit Sie einen guten Eindruck von Servlet 3.0 und seinen Auswirkungen auf die Java-Webentwicklung erhalten.

Asynchrone Unterstützung: Hintergrundkonzepte

Web 2.0-Technologien ändern das Verkehrsprofil zwischen Webclients (z. B. Browsern) und Webservern drastisch. Die in Servlet 3.0 eingeführte asynchrone Unterstützung wurde entwickelt, um auf diese neue Herausforderung zu reagieren. Um die Bedeutung der asynchronen Verarbeitung zu verstehen, betrachten wir zunächst die Entwicklung der HTTP-Kommunikation.

HTTP 1.0 bis HTTP 1.1

Eine wesentliche Verbesserung des HTTP 1.1-Standards sind dauerhafte Verbindungen . In HTTP 1.0 wird eine Verbindung zwischen einem Webclient und einem Server nach einem einzelnen Anforderungs- / Antwortzyklus geschlossen. In HTTP 1.1 wird eine Verbindung am Leben erhalten und für mehrere Anforderungen wiederverwendet. Permanente Verbindungen reduzieren die Kommunikationsverzögerung spürbar, da der Client die TCP-Verbindung nicht nach jeder Anforderung neu aushandeln muss.

Thread pro Verbindung

Es ist eine ständige Herausforderung für Anbieter, herauszufinden, wie Webserver skalierbarer gemacht werden können. Thread pro HTTP-Verbindung, die auf den dauerhaften Verbindungen von HTTP 1.1 basiert, ist eine gängige Lösung, die Anbieter übernommen haben. Bei dieser Strategie ist jede HTTP-Verbindung zwischen Client und Server einem Server auf der Serverseite zugeordnet. Threads werden aus einem vom Server verwalteten Thread-Pool zugewiesen. Sobald eine Verbindung geschlossen ist, wird der dedizierte Thread wieder in den Pool zurückgeführt und kann andere Aufgaben ausführen. Abhängig von der Hardwarekonfiguration kann dieser Ansatz auf eine hohe Anzahl gleichzeitiger Verbindungen skaliert werden. Experimente mit hochkarätigen Webservern haben zu numerischen Ergebnissen geführt, die zeigen, dass der Speicherverbrauch fast direkt proportional zur Anzahl der HTTP-Verbindungen zunimmt. Der Grund ist, dass Threads im Hinblick auf die Speichernutzung relativ teuer sind. Server, die mit einer festen Anzahl von Threads konfiguriert sind, können darunter leidenThread-Hunger- Problem, bei dem Anforderungen von neuen Clients abgelehnt werden, sobald alle Threads im Pool belegt sind.

Auf der anderen Seite fordern Benutzer bei vielen Websites nur sporadisch Seiten vom Server an. Dies wird als seitenweises Modell bezeichnet. Die Verbindungsthreads sind die meiste Zeit im Leerlauf, was eine Verschwendung von Ressourcen darstellt.

Thread pro Anfrage

Dank der nicht blockierenden E / A-Funktion, die in den neuen E / A-APIs von Java 4 für das Java Platform (NIO) -Paket eingeführt wurde, muss für eine dauerhafte HTTP-Verbindung nicht ständig ein Thread angeschlossen werden. Threads können Verbindungen nur zugewiesen werden, wenn Anforderungen verarbeitet werden. Wenn eine Verbindung zwischen Anforderungen inaktiv ist, kann der Thread recycelt werden, und die Verbindung wird in einem zentralen NIO-Auswahlsatz platziert, um neue Anforderungen zu erkennen, ohne einen separaten Thread zu verbrauchen. Dieses Modell wird als Thread pro Anforderung bezeichnetermöglicht es Webservern möglicherweise, eine wachsende Anzahl von Benutzerverbindungen mit einer festen Anzahl von Threads zu verarbeiten. Mit derselben Hardwarekonfiguration skalieren Webserver, die in diesem Modus ausgeführt werden, viel besser als im Thread-pro-Verbindung-Modus. Heutzutage verwenden beliebte Webserver - einschließlich Tomcat, Jetty, GlassFish (Grizzly), WebLogic und WebSphere - Threads pro Anforderung über Java NIO. Für Anwendungsentwickler ist die gute Nachricht, dass Webserver nicht blockierende E / A auf versteckte Weise implementieren, ohne dass Anwendungen über Servlet-APIs ausgesetzt sind.

Ajax-Herausforderungen meistern

Immer mehr Webanwendungen verwenden Ajax, um eine umfassendere Benutzererfahrung mit reaktionsschnelleren Benutzeroberflächen zu bieten. Benutzer von Ajax-Anwendungen interagieren viel häufiger mit dem Webserver als im seitenweisen Modell. Im Gegensatz zu normalen Benutzeranforderungen können Ajax-Anforderungen häufig von einem Client an den Server gesendet werden. Darüber hinaus können sowohl der Client als auch die auf der Clientseite ausgeführten Skripts den Webserver regelmäßig nach Updates abfragen. Mehr gleichzeitige Anforderungen führen dazu, dass mehr Threads verbraucht werden, wodurch der Vorteil des Thread-per-Request-Ansatzes in hohem Maße aufgehoben wird.

Langsam laufende, begrenzte Ressourcen

Einige langsam laufende Back-End-Routinen verschlechtern die Situation. Beispielsweise kann eine Anforderung durch einen erschöpften JDBC-Verbindungspool oder einen Webdienst-Endpunkt mit geringem Durchsatz blockiert werden. Bis die Ressource verfügbar wird, kann der Thread für eine lange Zeit mit der ausstehenden Anforderung hängen bleiben. Es ist besser, die Anforderung in einer zentralen Warteschlange zu platzieren, die auf verfügbare Ressourcen wartet, und diesen Thread zu recyceln. Dadurch wird die Anzahl der Anforderungsthreads effektiv gedrosselt, um der Kapazität der langsam laufenden Back-End-Routinen zu entsprechen. Es wird auch vorgeschlagen, dass zu einem bestimmten Zeitpunkt während der Anforderungsverarbeitung (wenn die Anforderung in der Warteschlange gespeichert ist) überhaupt keine Threads für die Anforderung verbraucht werden. Die asynchrone Unterstützung in Servlet 3.0 soll dieses Szenario durch einen universellen und portablen Ansatz erreichen, unabhängig davon, ob Ajax verwendet wird oder nicht. Listing 1 zeigt Ihnen, wie es funktioniert.

Listing 1. Drosselung des Zugriffs auf Ressourcen

@WebServlet(name="myServlet", urlPatterns={"/slowprocess"}, asyncSupported=true) public class MyServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { AsyncContext aCtx = request.startAsync(request, response); ServletContext appScope = request.getServletContext(); ((Queue)appScope.getAttribute("slowWebServiceJobQueue")).add(aCtx); } } @WebServletContextListener public class SlowWebService implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) { Queue jobQueue = new ConcurrentLinkedQueue(); sce.getServletContext().setAttribute("slowWebServiceJobQueue", jobQueue); // pool size matching Web services capacity Executor executor = Executors.newFixedThreadPool(10); while(true) { if(!jobQueue.isEmpty()) { final AsyncContext aCtx = jobQueue.poll(); executor.execute(new Runnable(){ public void run() { ServletRequest request = aCtx.getRequest(); // get parameteres // invoke a Web service endpoint // set results aCtx.forward("/result.jsp"); } }); } } } public void contextDestroyed(ServletContextEvent sce) { } }

Wenn das asyncSupportedAttribut auf festgelegt ist, truewird das Antwortobjekt beim Beenden der Methode nicht festgeschrieben. Beim Aufrufen wird startAsync()ein AsyncContextObjekt zurückgegeben, das das Anforderungs- / Antwortobjektpaar zwischenspeichert. Das AsyncContextObjekt wird dann in einer Warteschlange mit Anwendungsbereich gespeichert. Ohne Verzögerung doGet()kehrt die Methode zurück und der ursprüngliche Anforderungsthread wird recycelt. Im ServletContextListenerObjekt überwachen separate Threads, die während des Anwendungsstarts initiiert wurden, die Warteschlange und setzen die Anforderungsverarbeitung fort, sobald die Ressourcen verfügbar werden. Nachdem eine Anforderung verarbeitet wurde, haben Sie die Möglichkeit , die Antwort aufzurufen ServletResponse.getWriter().print(...)und anschließend complete()festzuschreiben oder forward()den Flow auf eine JSP-Seite zu leiten, die als Ergebnis angezeigt werden soll. Beachten Sie, dass JSP-Seiten Servlets mit einem sindasyncSupportedAttribut, das standardmäßig verwendet wird false.

Darüber hinaus bieten die Klassen AsyncEventund AsynListenerin Servlet 3.0 Entwicklern eine umfassende Kontrolle über asynchrone Lebenszyklusereignisse. Sie können eine AsynListenerüber die ServletRequest.addAsyncListener()Methode registrieren . Nachdem die startAsync()Methode für die Anforderung aufgerufen wurde, wird eine AsyncEventan die registrierte gesendet, AsyncListenersobald der asynchrone Vorgang abgeschlossen ist oder eine Zeitüberschreitung aufgetreten ist. Das AsyncEvententhält auch die gleichen Anforderungs- und Antwortobjekte wie im AsyncContextObjekt.

Server Push

A more interesting and vital use case for the Servlet 3.0 asynchronous feature is server push. GTalk, a widget that lets GMail users chat online, is an example of server push. GTalk doesn't poll the server frequently to check if a new message is available to display. Instead it waits for the server to push back new messages. This approach has two obvious advantages: low-lag communication without requests being sent, and no waste of server resources and network bandwidth.

Ajax allows a user to interact with a page even if other requests from the same user are being processed at the same time. A common use case is to have a browser regularly poll the server for updates of state changes without interrupting the user. However, high polling frequencies waste server resources and network bandwidth. If the server could actively push data to browsers -- in other words, deliver asynchronous messages to clients on events (state changes) -- Ajax applications would perform better and save precious server and network resources.

The HTTP protocol is a request/response protocol. A client sends a request message to a server, and the server replies with a response message. The server can't initiate a connection with a client or send an unexpected message to the client. This aspect of the HTTP protocol seemingly makes server push impossible. But several ingenious techniques have been devised to circumvent this constraint:

  • Service streaming (streaming) allows a server to send a message to a client when an event occurs, without an explicit request from the client. In real-world implementations, the client initiates a connection to the server through a request, and the response returns bits and pieces each time a server-side event occurs; the response lasts (theoretically) forever. Those bits and pieces can be interpreted by client-side JavaScript and displayed through the browser's incremental rendering ability.
  • Long polling, also known as asynchronous polling, is a hybrid of pure server push and client pull. It is based on the Bayeux protocol, which uses a topic-based publish-subscribe scheme. As in streaming, a client subscribes to a connection channel on the server by sending a request. The server holds the request and waits for an event to happen. Once the event occurs (or after a predefined timeout), a complete response message is sent to the client. Upon receiving the response, the client immediately sends a new request. The server, then, almost always has an outstanding request that it can use to deliver data in response to a server-side event. Long polling is relatively easier to implement on the browser side than streaming.
  • Passive piggyback: When the server has an update to send, it waits for the next time the browser makes a request and then sends its update along with the response that the browser was expecting.

Service streaming and long polling, implemented with Ajax, are known as Comet, or reverse Ajax. (Some developers call all interactive techniques reverse Ajax, including regular polling, Comet, and piggyback.)

Ajax improves single-user responsiveness. Server-push technologies like Comet improve application responsiveness for collaborative, multi-user applications without the overhead of regular polling.

Der Client-Aspekt der Server-Push-Techniken - wie versteckte iframes, XMLHttpRequestStreaming und einige Dojo- und jQuery-Bibliotheken, die die asynchrone Kommunikation erleichtern - liegt außerhalb des Geltungsbereichs dieses Artikels. Stattdessen liegt unser Interesse auf der Serverseite, insbesondere wie die Servlet 3.0-Spezifikation die Implementierung interaktiver Anwendungen mit Server-Push unterstützt.