Start - Publikationen - Wissen - TOGAF - Impressum -

Asynchronizität mit Service Streaming


HTTP ist ein Request-Response Client-Server Protokoll, basierend auf TCP. HTTP 1.0 beschrieb dafür einen klaren Ablauf: mit jedem Request wird eine TCP Connection zwischen zwischen Client und Server aufgebaut. An diese Connection bindet der Server einen Thread der den Response an den Client verschickt. Anschließend wird die TCP Connection beendet und der Thread steht im Server wieder als freie Ressource zur Verfügung.

Mit der Zeit wurde die Gestaltung der Webseiten anspruchsvoller, für ihre Darstellung mussten immer mehr Ressourcen (Bilder, Frames) nachgeladen werden. Das Aushandeln einer TCP Connection hat aber einen gewissen Overhead. Für das Nachladen von Ressourcen sollte deshalb eine bestehende Connection einfach weiter verwendet werden. Mit dem keep alive Header in HTTP 1.1 können sich deshalb Client und Server darüber einigen, eine TCP Connection für Wiederverwendung bestehen zu lassen. Zumindest mit Java hatte dies zur Folge, dass nun jede offen gehaltenen TCP Connection ein JVM Thread belegt, was als thread per connection bezeichnet wird. Eine offen gehaltene TCP Connection für sich belegt wenig Ressourcen und ist deshalb vollkommen akzeptabel. Solange mit einer solchen offen gehaltenen Connection Inhalte nachgeladen werden ist es auch akzeptabel, dass an diese TCP Connection ein Thread gebunden ist. Problematisch ist eine offene TCP Connection, die nichts tut: sie blockiert den assoziierten JVM Thread. Optimiert wurde dann in Java 1.4 mit dem NIO API. Das Redesign zielte unter anderem darauf ab, dass das Warten auf Ereignissen an IO-Sockets eine Angelegenheit des Betriebssystems bleibt. JVM Threads müssen dann nicht mehr warten (blockieren): sie arbeiten lediglich, wenn TCP Sockets das Eintreffen von Datenpaketen melden. Das ist der Grundgedanke einer nonblocking IO Schnittstelle.

Basierend auf diesem Entwicklungsstand (offene TCP Connections ohne JVM Last) kann über Asynchronizität neu nachgedacht werden. Offenbar ist es nun technisch akzeptabel, einen Response deutlich verzögert oder unvollständig mit verzögerter Vervollständigung an den Client zu schicken. Werden solche verspäteten Nachrichten mittels geeigneter Clienttechniken in den richtigen Kontext gesetzt, ist das aus Clientsicht ein Server Push und damit die Grundlage für echte Asynchronizität. Für diese Technik setzt sich der Begriff service streaming gerade durch, sie ist die Grundlage für "reverse AJAX" Implementierungen. (Oft in Kombination mit dem "unsauberen" polling und passive piggypack.) Egal ob absichtlich verzögert oder durch schleppende Abarbeitung, der JVM Thread muss vorübergehend vom Request abgekoppelt werden. Dafür gibt es schon länger proprietäre Lösungen (zum Beispiel der CometProcessor im Tomcat, Glassfish (Grizzly) mit CometEngine und Continuation in Jetty 6) die service streaming im produktiven Umfeld überhaupt erst möglich machen. Im Rahmen des JSR 315 wurde dies nun mit "Asynchronous processing" standardisiert.

Asynchronous processing mit Servlet 3.0


Servlet Methoden lassen sich mit Annotationen (oder dem Deploymentdeskriptor) als asynchron markieren. Nach dem Aufruf von

final AsyncContext asyncContext = request.startAsync(ServletRequest req, ServletResponse res);
wird der Request in einen asynchronen Zustand versetzt, was zwei wesentliche Konsequenzen hat. Zum einen wird der abarbeitende Thread vom Response abgekoppelt und beim Verlassen von service erfolgt KEIN commit. Zum anderen steht mit der AsyncContext Instanz nun Funktionalität zur nebenläufigen Abarbeitung zur Verfügung. Der Response wird abgschlossen, sobald complete an der AsyncContext Instanz gerufen wird (oder ein Fehler respektive Timeout eintritt). Insbesondere mit AsynchContext.start(Runnable) kann nun die eigentliche nebenläufige Abarbeitung angestoßen werden:
doGet(..) { 
  // Setzt die Abarbeitung in einen asynchronen Context
  request.setAsyncTimeout(10000); 
  final AsyncContext asyncContext = request.startAsync();
  asyncContext.start(new Runnable() {
    public void run() {
      // hier nebenläufige Abarbeitung mit der Hilfe
      // des asyncContext-Objekts
      :
      asyncContext.commit();
    }
  });
  // nach dieser Zeile erfolgt KEIN commit auf den Response!
} 
Serverseitig ist damit gutes Skalierverhalten trotz asynchroner Verarbeitung gegeben: der Thread der Request-Abarbeitung steht dem Container sofort nach Start der nebenläufigen Verarbeitung wieder zur Verfügung. Es muss aber geprüft werden, ob die im Einsatz befindlichen Infrastrukturkomponente (Firewalls, Proxys und Reverse Proxys) derart langlaufende TCP Connections überhaupt akzeptieren.

Referenzen


Asynchronous processing support in Servlet 3.0

copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved