Stufe 6Werkzeuge und Oberflächen · Lektion 31 von 34
Akka und Apache Pekko: Aktoren
Akka hat das Aktorenmodell auf der JVM populär gemacht: kleine, unabhängige Einheiten mit eigenem Zustand, die nur über Nachrichten kommunizieren. Seit dem Lizenzwechsel 2022 gibt es mit Apache Pekko einen freien Ableger, der dieselbe Idee unter Apache-Lizenz weiterführt.
Das Aktorenmodell
Ein Aktor ist ein Objekt, das
- einen privaten Zustand hat, auf den niemand sonst zugreift,
- ein Postfach besitzt, in dem Nachrichten eintreffen,
- Nachrichten eine nach der anderen verarbeitet – und dabei anderen Aktoren Nachrichten schicken, neue Aktoren erzeugen und sein Verhalten für die nächste Nachricht festlegen kann.
Weil ein Aktor immer nur eine Nachricht gleichzeitig bearbeitet und seinen Zustand mit niemandem teilt, braucht es keine Sperren. Tausende oder Millionen Aktoren können auf wenigen Threads laufen, und sie lassen sich sogar über mehrere Rechner verteilen.
Akka, Lizenz und Pekko
Akka wurde von Jonas Bonér ab 2009 entwickelt; die erste öffentliche Version erschien Anfang 2010. Es wurde zur Grundlage vieler Scala-Systeme und des Play Frameworks. Im September 2022 kündigte die Firma Lightbend an, Akka künftig nicht mehr unter der freien Apache-Lizenz 2.0, sondern unter der Business Source License zu veröffentlichen – für viele kommerzielle Nutzungen wurde damit eine Lizenzgebühr fällig. Nach drei Jahren fällt der jeweilige Code wieder unter die Apache-Lizenz.
Als Antwort entstand Apache Pekko, ein Fork der letzten Apache-lizenzierten Akka-Version. Die Apache Software Foundation hat Pekko im Mai 2024 zum eigenständigen Top-Level-Projekt erklärt. Die Programmierschnittstelle ist weitgehend identisch; beim Umstieg ändern sich vor allem Paketnamen (org.apache.pekko statt akka) und die Namen der Abhängigkeiten.
Einbinden
val pekkoVersion = "1.1.3"
libraryDependencies += "org.apache.pekko" %% "pekko-actor-typed" % pekkoVersionDie Versionsnummer übernehmen Sie am besten von der Projektseite; sie dient hier nur als Beispiel.
Ein typisierter Aktor
Die moderne Schnittstelle heißt Typed: Der Typ eines Aktors legt fest, welche Nachrichten er versteht. Falsche Nachrichten erkennt so schon der Compiler.
import org.apache.pekko.actor.typed.{ActorRef, ActorSystem, Behavior}
import org.apache.pekko.actor.typed.scaladsl.Behaviors
object Zaehler:
enum Befehl:
case Erhoehen(um: Int)
case Abfragen(antwortAn: ActorRef[Int])
import Befehl.*
def apply(stand: Int = 0): Behavior[Befehl] =
Behaviors.receiveMessage {
case Erhoehen(n) =>
Zaehler(stand + n) // neues Verhalten mit neuem Stand
case Abfragen(an) =>
an ! stand // Antwort schicken
Behaviors.same
}Einige Dinge fallen auf:
- Die Nachrichten sind ein enum – der Compiler prüft, dass jeder Fall behandelt wird.
- Der Zustand
standist keine veränderliche Variable. Stattdessen liefert der Aktor nach jeder Nachricht sein nächstes Verhalten zurück, hier einen Zähler mit neuem Stand. Das ersetzt dasbecome/unbecomeder älteren, untypisierten Schnittstelle. !(gesprochen „tell“) schickt eine Nachricht, ohne auf eine Antwort zu warten.
Das System starten
@main def start(): Unit =
val system: ActorSystem[Zaehler.Befehl] = ActorSystem(Zaehler(), "zaehler")
system ! Zaehler.Befehl.Erhoehen(5)
system ! Zaehler.Befehl.Erhoehen(3)Um eine Antwort zu erhalten, braucht der Absender selbst eine Adresse (ActorRef[Int]). Dafür gibt es das Ask-Muster, das eine Antwort als Future liefert, oder man lässt einen anderen Aktor fragen, der die Antwort als Nachricht bekommt.
Aufsicht: Let it crash
Aktoren bilden eine Hierarchie: Jeder Aktor wird von einem Elternaktor erzeugt und von ihm beaufsichtigt. Wirft ein Aktor eine Ausnahme, entscheidet eine Aufsichtsstrategie, was passiert – etwa neu starten, anhalten oder mit Verzögerung neu starten:
Behaviors.supervise(Zaehler())
.onFailure[IllegalStateException](SupervisorStrategy.restart)Die Philosophie dahinter stammt aus der Sprache Erlang und heißt let it crash: Statt jeden denkbaren Fehler an Ort und Stelle abzufangen, lässt man einen fehlerhaften Aktor abstürzen und startet ihn in einem sauberen Zustand neu.
Mehr als Aktoren
Rund um den Kern gibt es Module für viele Aufgaben:
| Modul | Zweck |
|---|---|
| Streams | Datenströme mit Gegendruck (backpressure) |
| HTTP | HTTP-Server und -Clients auf Basis von Streams |
| Cluster | Aktoren über mehrere Rechner verteilen |
| Persistence | Zustand über Ereignisse speichern (event sourcing) |
Wann Aktoren?
Aktoren glänzen bei langlebigen Einheiten mit Zustand, bei vielen gleichzeitigen Verbindungen und bei verteilten Systemen. Für eine einzelne Berechnung, die im Hintergrund läuft und ein Ergebnis liefert, genügt ein Future. Und für einfache Webanwendungen müssen Sie Aktoren gar nicht selbst schreiben – Play nutzt sie im Hintergrund.