Reactor – Asynchrones Framework von SpringSource

Eberhard Wolff

Asynchrone Programmierung wird zunehmend wichtiger – nur so können Anwendungen mit vielen mobilen Clients oder durch WebSockets verbundene Web Browsern performant implementiert werden. Frameworks wie Akka oder vert.x unterstützen schon solche Modelle. Nun betritt SpringSource mit Reactor diesen Markt – da lohnt sich sicher ein erster Blick!

Vorab aber eine Warnung: Reactor [1] ist noch in einer sehr frühen Entwicklungsphase – es kann also ohne weiteres sein, dass sich noch vieles ändert. Für einen ersten Blick reicht der aktuelle Stand aber auf jeden Fall aus. Reactor positioniert sich als Basis-Technologie: Das Framework soll in erster Linie die Basis für andere Projekte wie Spring Integration, Spring Batch und Spring XD darstellen, die alle eine asynchrone Verarbeitung benötigen. Reactor könnte dann mit verschiedenen asynchronen Systemen wie Netty oder Servlet 3.0 kombiniert werden.

Asynchrone Programmierung zeichnet sich dadurch aus, dass für die Verarbeitung von Events jeweils eine bestimmte Logik hinterlegt wird. Die Events werden asynchron verarbeitet: Wenn die benötigten Ressourcen frei sind, wird die dafür notwendige Logik aufgerufen. Die Logik selber wird klassisch synchron implementiert. Dadurch ist das Programmiermodell einfach: Die Logik selber ist synchron und muss sich nicht um Parallelität kümmern. Die asynchrone Verarbeitung wird durch das Framework sichergestellt. So können die Aufgaben in eine Warteschlange eingereiht werden, so dass nicht für jede potentiell parallele Aufgabe ein Thread blockiert werden muss – gleichzeitig kann eine bestimmte Menge an Aufgaben parallel abgearbeitet werden. Der Ansatz kommt dann mit relativ wenigen Threads aus. Dieses Vorgehen stammt aus dem Reactor-Pattern [2], das dem Framework auch den Namen gegeben hat.

Bei dem Reactor-Framework gibt es also zunächst drei wesentliche Bestandteile:

·       Events werden verschickt und behandelt.

·       Consumer erhalten die Events und können sinnvoll auf sie reagieren.

·       Der Selector entscheidet, welche Events ein Consumer bekommt.

Listing 1 zeigt ein sehr einfaches Beispiels: Zunächst wird ein Reactor erzeugt. Dort wird dann ein Consumer registriert. Der Selector wird durch ein $ erzeugt. Das ist eine durch jQuery inspiriert abkürzende Schreibweise – es wäre auch möglich, ein Selector selber zu erzeugen. Am Ende wird dann an den Reactor ein Event gegeben, das anschließend asynchron an den Consumer weitergegeben wird.

Reactor reactor = new Reactor();
reactor.on($("hello"), new Consumer<Event<String>>() {
  public void accept(Event<String> t) {
    // Logik
  }
reactor.notify("hello");
 

Der Reactor kann intern verschiedene Dispatcher nutzen, um die Events asynchron an Consumer zu verteilen. Beispielsweise kann eine BlockingQueue oder ein ThreadPool genutzt werden. Ebenso gibt es eine Implementierung auf Basis des RingBuffers aus dem Disruptor Projekt [4]. SpringSource selber gibt an, dass mit schnellen Dispatcher-Implementierungen auch auf weniger leistungsfähiger Hardware bis zu 15 Millionen Events pro Sekunde ausgeführt werden können.

Bei komplexerer Logik wird solcher Code schnell schwer lesbar, weil eine Vielzahl von Callbacks implementiert wird – wie im Beispiel der Consumer. Gerade das Zusammenschalten mehrerer Consumer ist komplex. Das Reactor-Framework bietet daher die Möglichkeit an, Funktionen zu komponieren. Dazu dienen die Klasse Composable und das Interface Function.

Composable<Integer> c = R.compose("42").get()
  .map(new Function<String, Integer>() {
     public Integer apply(String s) {
       return Integer.decode(s);
     }
  }).consume(new Consumer<Integer>() {
    public void accept(Integer event) {
}
  });

Ein Beispiel zeigt Listing 2: Zunächst wird mit der statischen Methode compose der Klasse R ein Builder erzeugt. Er vereinfacht das Erzeugen von Composables. In diesem Fall soll das Composable nur den String „42“ als Eingabe bekommen, so dass sofort auf den Builder get aufgerufen wird. Das Ergebnis ist ein Composable, dem mit map eine Funktion zugewiesen wird, die aus dem String ein Integer macht. Dieser Wert kann dann von dem Consumer verarbeitet werden.

Das interessante ist nun, dass jeder dieser Schritte – also die Umwandlung wie auch das Verarbeiten – durch eine der schon bekannten Dispatcher-Implementierungen asynchron verarbeitet werden kann.

Das Programmiermodell ist natürlich mit den Lambda-Ausdrücken aus Java 8 oder den Closures in Groovy wesentlich einfacher. Dann können die Funktionen direkt hingeschrieben werden.

Fazit

Reactor ist eine Basis-Technologie. Daher ist vor allem die Integration in andere Projekte spannend. Beispielsweise soll neben der Integration in Spring Integration, Spring Batch und Spring XD auch ein Event-System in Grails implementiert werden, das auf Reactor basiert. Weitere Anwendungen sind schon sichtbar: So entsteht gerade eine Unterstützung für LogBack und TCP Verbindungen. Ebenso wird daran gearbeitet, eine Spring-Integration für Reactor bereitzustellen. Wir werden uns bald Reactor im Java Magazin genauer ansehen.

Geschrieben von
Eberhard Wolff
Eberhard Wolff
Eberhard Wolff ist Fellow bei innoQ und arbeitet seit mehr als fünfzehn Jahren als Architekt und Berater, oft an der Schnittstelle zwischen Business und Technologie. Er ist Autor zahlreicher Artikel und Bücher, u.a. zu Continuous Delivery und Microservices und trägt regelmäßig als Sprecher auf internationalen Konferenz vor. Sein technologischer Schwerpunkt sind moderne Architektur- und Entwicklungsansätze wie Cloud, Continuous Delivery, DevOps, Microservices und NoSQL.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.