Einführung in Akka Actors in Java

Einführung in Akka Actors in Java

1. Einführung

Akka is an open-source library that helps to easily develop concurrent and distributed applications verwenden Java oder Scala, indem sie das Akteurmodell nutzen.

In diesem Tutorial werdenwe’ll present the basic features like defining actors, how they communicate and how we can kill them. In den letzten Anmerkungen werden wir auch einige bewährte Methoden für die Arbeit mit Akka erwähnen.

2. Das Schauspielermodell

Das Schauspieler-Modell ist für die Informatik-Community nicht neu. Es wurde erstmals 1973 von Carl Eddie Hewitt als theoretisches Modell für den Umgang mit gleichzeitigen Berechnungen eingeführt.

Es begann, seine praktische Anwendbarkeit zu demonstrieren, als die Softwareindustrie begann, die Fallstricke der Implementierung von gleichzeitigen und verteilten Anwendungen zu erkennen.

An actor represents an independent computation unit. Einige wichtige Merkmale sind:

  • Ein Akteur kapselt seinen Zustand und einen Teil der Anwendungslogik

  • Akteure interagieren nur durch asynchrone Nachrichten und niemals durch direkte Methodenaufrufe

  • Jeder Akteur hat eine eindeutige Adresse und ein Postfach, in dem andere Akteure Nachrichten übermitteln können

  • Der Akteur verarbeitet alle Nachrichten in der Mailbox in sequenzieller Reihenfolge (die Standardimplementierung der Mailbox ist eine FIFO-Warteschlange).

  • Das Akteurensystem ist in einer baumartigen Hierarchie organisiert

  • Ein Akteur kann andere Akteure erstellen, Nachrichten an jeden anderen Akteur senden und sich selbst anhalten, oder jeder Akteur, der erstellt wurde

2.1. Vorteile

Die gleichzeitige Entwicklung von Anwendungen ist schwierig, da wir uns mit Synchronisation, Sperren und gemeinsamem Speicher befassen müssen. Durch die Verwendung von Akka-Akteuren können wir problemlos asynchronen Code schreiben, ohne dass Sperren und Synchronisierungen erforderlich sind.

Einer der Vorteile der Verwendung von Nachrichten anstelle von Methodenaufrufen besteht darin, dassthe sender thread won’t block to wait for a return value when it sends a message to another actor. Der empfangende Akteur antwortet mit dem Ergebnis, indem er eine Antwortnachricht an den Absender sendet.

Ein weiterer großer Vorteil der Verwendung von Nachrichten besteht darin, dass wir uns nicht um die Synchronisierung in einer Multithread-Umgebung kümmern müssen. Dies liegt an der Tatsache, dassall the messages are processed sequentially.

Ein weiterer Vorteil des Akka-Darstellermodells ist die Fehlerbehandlung. Durch das Organisieren der Akteure in einer Hierarchie kann jeder Akteur seinen Elternteil über das Versagen informieren und dementsprechend handeln. Der übergeordnete Akteur kann entscheiden, ob die untergeordneten Akteure angehalten oder neu gestartet werden sollen.

3. Konfiguration

Um die Akka-Akteure zu nutzen, müssen wir die folgende Abhängigkeit vonMaven Central hinzufügen:


    com.typesafe.akka
    akka-actor_2.12
    2.5.11

4. Einen Schauspieler erstellen

Wie bereits erwähnt, sind die Akteure in einem Hierarchiesystem definiert. Alle Akteure, die eine gemeinsame Konfiguration haben, werden durchActorSystem. definiert

Im Moment definieren wir einfach einActorSystem mit der Standardkonfiguration und einem benutzerdefinierten Namen:

ActorSystem system = ActorSystem.create("test-system");

Obwohl wir noch keine Akteure erstellt haben, enthält das System bereits drei Hauptakteure:

  • Der Root-Guardian-Akteur hat die Adresse „/“, die, wie der Name sagt, die Wurzel der Akteurensystemhierarchie darstellt

  • der User Guardian Actor hat die Adresse "/ user". Dies ist das übergeordnete Element aller von uns definierten Akteure

  • der Akteur des Systemwächters mit der Adresse „/ system“. Dies ist das übergeordnete Element für alle vom Akka-System intern definierten Akteure

Jeder Akka-Akteur erweitert die abstrakte Klasse vonAbstractActorund implementiert die Methode voncreateReceive()für die Verarbeitung eingehender Nachrichten von anderen Akteuren:

public class MyActor extends AbstractActor {
    public Receive createReceive() {
        return receiveBuilder().build();
    }
}

This is the most basic actor we can create. Es kann Nachrichten von anderen Akteuren empfangen und verwirft diese, da inReceiveBuilder. keine übereinstimmenden Nachrichtenmuster definiert sind. Wir werden später in diesem Artikel über den Nachrichtenmusterabgleich sprechen.

Nachdem wir unseren ersten Schauspieler erstellt haben, sollten wir ihn inActorSystem aufnehmen:

ActorRef readingActorRef
  = system.actorOf(Props.create(MyActor.class), "my-actor");

4.1. Schauspieler-Konfiguration

The Props class contains the actor configuration. Wir können Dinge wie den Dispatcher, das Postfach oder die Bereitstellungskonfiguration konfigurieren. Diese Klasse ist unveränderlich und daher threadsicher, sodass sie beim Erstellen neuer Akteure gemeinsam genutzt werden kann.

Es wird dringend empfohlen und als bewährte Methode angesehen, die Factory-Methoden innerhalb des Actor-Objekts zu definieren, die die Erstellung desProps-Objekts übernehmen.

Definieren wir als Beispiel einen Akteur, der eine Textverarbeitung durchführt. Der Akteur erhält einString-Objekt, für das er die Verarbeitung durchführt:

public class ReadingActor extends AbstractActor {
    private String text;

    public static Props props(String text) {
        return Props.create(ReadingActor.class, text);
    }
    // ...
}

Um eine Instanz dieses Aktortyps zu erstellen, verwenden wir einfach die Factory-Methodeprops(), um das ArgumentStringan den Konstruktor zu übergeben:

ActorRef readingActorRef = system.actorOf(
  ReadingActor.props(TEXT), "readingActor");

Nachdem wir nun wissen, wie ein Akteur definiert wird, wollen wir sehen, wie er innerhalb des Akteursystems kommuniziert.

5. Actor Messaging

Um miteinander zu interagieren, können die Akteure Nachrichten von jedem anderen Akteur im System senden und empfangen. Diesemessages can be any type of object with the condition that it’s immutable.

It’s a best practice to define the messages inside the actor class. Dies hilft beim Schreiben von Code, der leicht zu verstehen ist und weiß, mit welchen Nachrichten ein Akteur umgehen kann.

5.1. Nachrichten senden

Innerhalb des Akka-Actor-Systems werden Nachrichten mit folgenden Methoden gesendet:

  • sagen()

  • Fragen()

  • nach vorne()

When we want to send a message and don’t expect a response, we can use the tell() method. Dies ist aus Sicht der Leistung die effizienteste Methode:

readingActorRef.tell(new ReadingActor.ReadLines(), ActorRef.noSender());

Der erste Parameter stellt die Nachricht dar, die wir an die AkteursadressereadingActorRef senden.

Der zweite Parameter gibt an, wer der Absender ist. Dies ist nützlich, wenn der Akteur, der die Nachricht empfängt, eine Antwort an einen anderen Akteur als den Absender senden muss (z. B. das übergeordnete Element des sendenden Akteurs).

Normalerweise können wir den zweiten Parameter aufnull oderActorRef.noSender() setzen, da wir keine Antwort erwarten. When we need a response back from an actor, we can use the ask() method:

CompletableFuture future = ask(wordCounterActorRef,
  new WordCounterActor.CountWords(line), 1000).toCompletableFuture();


Wenn Sie eine Antwort von einem Akteur anfordern, wird einCompletionStage-Objekt zurückgegeben, sodass die Verarbeitung nicht blockiert.

Eine sehr wichtige Tatsache, auf die wir achten müssen, ist die Fehlerbehandlung durch den Akteur, der darauf reagiert. To return a Future object that will contain the exception we must send a Status.Failure message to the sender actor.

Dies erfolgt nicht automatisch, wenn ein Akteur während der Verarbeitung einer Nachricht eine Ausnahme auslöst und der Aufruf vonask()eine Zeitüberschreitung aufweist und in den Protokollen kein Verweis auf die Ausnahme angezeigt wird:

@Override
public Receive createReceive() {
    return receiveBuilder()
      .match(CountWords.class, r -> {
          try {
              int numberOfWords = countWordsFromLine(r.line);
              getSender().tell(numberOfWords, getSelf());
          } catch (Exception ex) {
              getSender().tell(
               new akka.actor.Status.Failure(ex), getSelf());
               throw ex;
          }
    }).build();
}

Wir haben auch dieforward()-Methode, dietell() ähnlich ist. Der Unterschied besteht darin, dass der ursprüngliche Absender der Nachricht beim Senden der Nachricht beibehalten wird, sodass der die Nachricht weiterleitende Akteur nur als Vermittler fungiert:

printerActorRef.forward(
  new PrinterActor.PrintFinalResult(totalNumberOfWords), getContext());

5.2. Nachrichten empfangen

Each actor will implement the createReceive() method, das alle eingehenden Nachrichten verarbeitet. DasreceiveBuilder() verhält sich wie eine switch-Anweisung und versucht, die empfangene Nachricht dem definierten Nachrichtentyp zuzuordnen:

public Receive createReceive() {
    return receiveBuilder().matchEquals("printit", p -> {
        System.out.println("The address of this actor is: " + getSelf());
    }).build();
}

When received, a message is put into a FIFO queue, so the messages are handled sequentially.

6. Einen Schauspieler töten

Als wir die Verwendung eines Akteurswe can stop it by calling the stop() method über dieActorRefFactory-Schnittstelle beendet haben:

system.stop(myActorRef);

Wir können diese Methode verwenden, um einen untergeordneten Schauspieler oder den Schauspieler selbst zu beenden. Es ist wichtig zu beachten, dass das Stoppen asynchron erfolgt und dasscurrent message processing will finish vor dem Beenden des Akteurs angegeben werden. No more incoming messages will be accepted in the actor mailbox.

Nachstopping a parent actor,we’ll also send a kill signal to all of the child actors, die von ihm erzeugt wurden.

Wenn wir das Akteursystem nicht mehr benötigen, können wir es beenden, um alle Ressourcen freizugeben und Speicherverluste zu vermeiden:

Future terminateResponse = system.terminate();

Dadurch werden die Systemwächter-Akteure gestoppt, daher alle Akteure, die in diesem Akka-System definiert sind.

We could also send a PoisonPill message an jeden Schauspieler, den wir töten wollen:

myActorRef.tell(PoisonPill.getInstance(), ActorRef.noSender());

Die NachrichtPoisonPillwird vom Akteur wie jede andere Nachricht empfangen und in die Warteschlange gestellt. The actor will process all the messages until it gets to the PoisonPill one. Erst dann beginnt der Akteur mit dem Kündigungsprozess.

Eine weitere spezielle Nachricht, die zum Töten eines Schauspielers verwendet wird, ist die NachrichtKill. Im Gegensatz zuPoisonPill, wirft der Akteur bei der Verarbeitung dieser Nachricht einActorKilledException:

myActorRef.tell(Kill.getInstance(), ActorRef.noSender());

7. Fazit

In diesem Artikel haben wir die Grundlagen des Akka-Frameworks vorgestellt. Wir haben gezeigt, wie man Akteure definiert, wie sie miteinander kommunizieren und wie man sie beendet.

Wir schließen mit einigen Best Practices bei der Arbeit mit Akka:

  • Verwenden Sietell() anstelle vonask(), wenn die Leistung ein Problem darstellt

  • Bei Verwendung vonask() sollten Ausnahmen immer durch Senden einerFailure-Nachricht behandelt werden

  • Akteure sollten keinen veränderlichen Staat teilen

  • Ein Schauspieler sollte nicht innerhalb eines anderen Schauspielers deklariert werden

  • actors aren’t stopped automatically, wenn nicht mehr auf sie verwiesen wird. Wir müssen einen Akteur explizit zerstören, wenn wir ihn nicht mehr benötigen, um Speicherverluste zu vermeiden

  • Von Akteuren verwendete Nachrichtenshould always be immutable

Wie immer ist der Quellcode für den Artikelover on GitHub verfügbar.