Open Source im professionellen Einsatz
Linux-Magazin 02/2007
© sxc.hu, Yarick Mission

© sxc.hu, Yarick Mission

Asynchrone Webanwendungen mit Jetty, Comet und anderen

Web-Beschleunigung

Nach der Ajax-Revolution im Browser steht eine Modernisierung der Server-Seite an. Auch dort sollen asynchrone Requests zu schnelleren und besser skalierenden Webanwendungen führen. Der Jetty-Server hat dazu Continuations implementiert, Cometd bietet einen Standard.

714

Noch haben sich nicht alle Webentwickler auf das Ajax-Modell eingestellt, da steht die nächste Aufgabe schon vor der Tür: Soll der Browser eine geöffnete Webseite möglichst aktuell halten, muss er sich regelmäßig über Veränderungen am Datenbestand auf dem Server informieren. Dazu läuft meist im Hintergrund eine Javascript-Funktion, die in einem festgelegten, möglichst kleinen Intervall den Server nach Änderungen befragt (so genanntes Polling).

Dumm nur, wenn sich eine Zeit lang überhaupt nichts ändert, dann waren nämlich alle Anfragen vergebens. Angenommen das Polling-Intervall liegt bei 5 Sekunden, die Daten ändern sich aber 2 Minuten lang nicht, dann sind das 24 überflüssige Requests - wohlgemerkt pro Client und Browser.

Effektiver wäre es, wenn der Server von sich aus dem Client mitteilen könnte, dass sich die interessierenden Daten geändert haben. Leider geht das nicht so einfach: Der HTTP-Verkehr gehorcht dem Request-Response-Modell, bei dem der Request vom Client ausgeht. Ein möglicher Ausweg wäre, die Verbindung zwischen Client und Server bestehen zu lassen, sodass der Server seine Antwort erst dann schickt, wenn es auch etwas Sinnvolles zu antworten gibt.

Das ist tatsächlich möglich, da die meisten Browser und Server über HTTP 1.1 kommunizieren, bei dem so genannte Keep-Alive-Connections Standard sind. So wird aus dem bislang synchronen (Server antwortet sofort auf Request) ein asynchroner Ablauf. Die Threads, Prozesse und so fort, die auf Server-Seite eine Anfrage bedienen, müssen dann entsprechend länger laufen.

Damit das nicht zu einem neuen Ressourcenengpass führt, sind weitere kluge Ideen gefragt. Eine hat Greg Wilkins in dem in Java geschriebenen Jetty-Server implementiert. Jetty [1] ist ein Webserver und Servlet-Container, der sich durch seine geringe Größe auszeichnet. Deshalb setzen ihn viele bekannte J2EE-Server wie Jonas, Geronimo und Jboss als Servlet-Container ein.

Jetty-Continuations

Greg Wilkins, der in seinem Blog [2] fleißig über neue asynchrone Webtechniken berichtet, verwendet für asynchrone Anfragen mit Jetty so genannte Continuations. Bekannt sind diese bisher vor allem von experimentellen Servern aus der Ruby- und Smalltalk-Ecke, etwa Borges [3]. Die Motivation für die Entwicklung einer eigenen Lösung war, dass Java-Webserver für jede neue Verbindung einen neuen Thread erzeugen.

Obwohl die JVM in den letzten Jahren in diesem Bereich stark optimiert wurde, bedeuten viele Threads immer noch einen relativ großen Verbrauch an Ressourcen wie Speicher - auch wenn ein Thread mit »wait()« schlafen gelegt wird. Zudem begrenzen die meisten Server die maximale Anzahl an Threads mit einem harten Limit, sodass bei dessen Erreichen er einfach keine neuen Verbindungen mehr annimmt.

Extra API

Die von Wilkins implementierten Jetty-Continuations [4] erlauben es, die Bearbeitung eines Requests auszusetzen oder auf später zu verschieben, den zugehörigen Thread aber für einen neuen Request zu verwenden. Um davon Gebrauch zu machen, müssen Webprogrammierer explizit die Continuations-Klassen verwenden und ihre Anwendungen entsprechend umstrukturieren.

So liefert die Helferklasse »ContinuationSupport.getContinuation()« eine neue Continuation, die man mit »suspend()« aussetzt und mit »resume()« wieder aufnimmt. Listing 1 zeigt die Implementation der entsprechenden Methode des Chat-Beispiels von Jetty. Daran ist zu sehen, dass es sich bei der Jetty-Implementation gar nicht um echte Continuations handelt, wie sie von anderen Programmiersprachen bekannt sind.

Listing 1: »Chat.java«

01 private void doPoll(HttpServletRequest request, AjaxResponse response)
02     {
03         HttpSession session = request.getSession(true);
04         String id = session.getId();
05         long timeoutMS = 10000L;
06         if (request.getParameter("timeout")!=null)
07             timeoutMS=Long.parseLong(request.getParameter("timeout"));
08 
09         Member member=null;
10         synchronized (mutex)
11         {
12             member = (Member)chatroom.get(id);
13             if (member==null)
14             {
15                 member = new Member(session,null);
16                 chatroom.put(session.getId(),member);
17             }
18 
19             // Get an existing Continuation or create a new one if there are no events.
20             if (!member.hasMessages())
21             {
22                 Continuation continuation = ContinuationSupport.getContinuation(request, mutex);
23                 member.setContinuation(continuation);
24                 continuation.suspend(timeoutMS);
25             }
26             member.setContinuation(null);
27 
28             if (member.sendMessages(response))
29                 sendMembers(response);
30         }
31 
32     }

Die merken sich nämlich die Stelle, an der der Programmfluss die Continuation-Subroutine verlässt, und springen bei einem erneuten Aufruf genau dorthin. Weil das mit den Jetty-Continuations nicht funktioniert, muss der Entwickler selbst dafür sorgen, dass sich die aufgerufene Funktion bei jedem Aufruf gleich verhält, im Jargon "idempotent" ist.

Wilkins verwendet in seinem Beispiel dazu das Chatroom-Objekt, in dem er beim ersten Aufruf die Session-ID speichert. Wenn keine Nachrichten anstehen, setzt die Methode die Verarbeitung aus. Bei einem weiteren Aufruf steht die ID wieder über das Chatroom-Objekt zur Verfügung (Zeile 12).

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Härtetest

    Wer Client- und Server-seitig im Web mitmischen will, braucht strapazierfähige Programmierbibliotheken, zündende Ideen, ein gerüttelt Maß an Know-how und - einen Browser. Folgerichtig beginnt dieser Linux-Magazin-Schwerpunkt mit einem strengen Test von Firefox, Konqueror, Opera & Co.

  • Node.js

    Die Javascript-basierte Programmierumgebung für Webentwicklung Node.js bändigt den Ressourcen-Hunger von Echtzeit-Webanwendungen wie Onlinespielen oder Chatservern. Dank eines ereignisorientierten Ansatzes kommt Node.js dabei mit einem Webserver-Thread pro Instanz aus.

  • Reiches Angebot

    Wer in die Webprogrammierung mit Java einsteigen will oder muss, braucht einen Überblick über die Fülle der Standards, Technologien und Anwendungsserver. Dieser Artikel stellt die wichtigsten vor und erläutert, für welche Aufgaben sie sich am besten eignen.

  • R und Python

    Die statistische Programmiersprache R seziert eine Datenbasis nach allen Regeln der Kunst. Eingebettet in Python und unterstützt von diversen Webtechnologien bereitet sie aber auch Daten passend für den Webbrowser auf – im Beispiel solche zur Laufbahn von Kometen.

  • Java am Ende?

    Die Popularität von Java sinkt stetig, während dynamische Sprachen wie Ruby immer beliebter werden. Nun springt selbst Sun auf den Zug auf und stellt die Hauptentwickler von Jruby ein.

comments powered by Disqus

Ausgabe 10/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.