Introduction aux acteurs Akka en Java

Introduction aux acteurs Akka en Java

1. introduction

Akka is an open-source library that helps to easily develop concurrent and distributed applications utilisant Java ou Scala en exploitant le modèle d'acteur.

Dans ce didacticiel,we’ll present the basic features like defining actors, how they communicate and how we can kill them. Dans les notes finales, nous noterons également quelques bonnes pratiques lors de l'utilisation d'Akka.

2. Le modèle de l'acteur

Le modèle d'acteur n'est pas nouveau dans la communauté informatique. Il a été introduit pour la première fois par Carl Eddie Hewitt en 1973, en tant que modèle théorique de traitement des calculs concurrents.

Il a commencé à montrer son applicabilité pratique lorsque l'industrie du logiciel a commencé à comprendre les pièges de la mise en œuvre d'applications concurrentes et distribuées.

An actor represents an independent computation unit. Certaines caractéristiques importantes sont:

  • un acteur encapsule son état et une partie de la logique d'application

  • les acteurs n'interagissent que par des messages asynchrones et jamais par des appels de méthode directs

  • chaque acteur a une adresse unique et une boîte aux lettres dans laquelle d'autres acteurs peuvent transmettre des messages

  • l'acteur traitera tous les messages de la boîte aux lettres dans un ordre séquentiel (l'implémentation par défaut de la boîte aux lettres étant une file d'attente FIFO)

  • le système d'acteurs est organisé dans une hiérarchie arborescente

  • un acteur peut créer d'autres acteurs, peut envoyer des messages à tout autre acteur et s'arrêter ou tout acteur est créé

2.1. Avantages

Le développement d'applications simultanées est difficile car nous devons gérer la synchronisation, les verrous et la mémoire partagée. En utilisant des acteurs Akka, nous pouvons facilement écrire du code asynchrone sans avoir besoin de verrous et de synchronisation.

L'un des avantages de l'utilisation d'un message au lieu d'appels de méthode est quethe sender thread won’t block to wait for a return value when it sends a message to another actor. L'acteur destinataire répondra avec le résultat en envoyant un message de réponse à l'expéditeur.

Un autre grand avantage de l'utilisation des messages est que nous n'avons pas à nous soucier de la synchronisation dans un environnement multithread. Ceci est dû au fait queall the messages are processed sequentially.

Un autre avantage du modèle d'acteur Akka est la gestion des erreurs. En organisant les acteurs dans une hiérarchie, chaque acteur peut informer son parent de l'échec afin qu'il puisse agir en conséquence. L'acteur parent peut décider d'arrêter ou de redémarrer les acteurs enfants.

3. Installer

Pour tirer parti des acteurs Akka, nous devons ajouter la dépendance suivante à partir deMaven Central:


    com.typesafe.akka
    akka-actor_2.12
    2.5.11

4. Créer un acteur

Comme mentionné, les acteurs sont définis dans un système de hiérarchie. Tous les acteurs qui partagent une configuration commune seront définis par unActorSystem.

Pour l'instant, nous allons simplement définir unActorSystem avec la configuration par défaut et un nom personnalisé:

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

Même si nous n’avons encore créé aucun acteur, le système contiendra déjà 3 acteurs principaux:

  • l'acteur gardien gardien ayant l'adresse «/» qui, comme son nom l'indique, représente la racine de la hiérarchie du système d'acteurs

  • l'acteur tuteur de l'utilisateur ayant l'adresse «/ utilisateur». Ce sera le parent de tous les acteurs que nous définissons

  • l'acteur gardien du système ayant l'adresse «/ système». Ce sera le parent de tous les acteurs définis en interne par le système Akka.

Tout acteur Akka étendra la classe abstraiteAbstractActor et implémentera la méthodecreateReceive() pour gérer les messages entrants des autres acteurs:

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

This is the most basic actor we can create. Il peut recevoir des messages d'autres acteurs et les rejettera car aucun modèle de message correspondant n'est défini dans leReceiveBuilder. Nous parlerons de la correspondance de modèle de message plus loin dans cet article.

Maintenant que nous avons créé notre premier acteur, nous devons l'inclure dans lesActorSystem:

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

4.1. Configuration des acteurs

The Props class contains the actor configuration. Nous pouvons configurer des éléments comme le répartiteur, la boîte aux lettres ou la configuration de déploiement. Cette classe est immuable, donc thread-safe, elle peut donc être partagée lors de la création de nouveaux acteurs.

Il est fortement recommandé et considéré comme une meilleure pratique de définir les méthodes de fabrique à l’intérieur de l’objet acteur qui gèreront la création de l’objetProps.

Pour illustrer, définissons un acteur qui effectuera un traitement de texte. L'acteur recevra un objetString sur lequel il effectuera le traitement:

public class ReadingActor extends AbstractActor {
    private String text;

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

Maintenant, pour créer une instance de ce type d'acteur, nous utilisons simplement la méthode de fabriqueprops() pour passer l'argumentString au constructeur:

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

Maintenant que nous savons comment définir un acteur, voyons comment il communique au sein du système d'acteurs.

5. Messagerie d'acteur

Pour interagir les uns avec les autres, les acteurs peuvent envoyer et recevoir des messages de n’importe quel autre acteur du système. Cesmessages 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. Cela permet d'écrire un code facile à comprendre et de savoir quels messages un acteur peut gérer.

5.1. Envoi de messages

Dans le système d'acteurs Akka, les messages sont envoyés à l'aide de méthodes:

  • dire()

  • demander()

  • vers l'avant()

When we want to send a message and don’t expect a response, we can use the tell() method. C'est la méthode la plus efficace du point de vue des performances:

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

Le premier paramètre représente le message que nous envoyons à l'adresse d'acteurreadingActorRef.

Le deuxième paramètre spécifie qui est l'expéditeur. Ceci est utile lorsque l'acteur recevant le message doit envoyer une réponse à un acteur autre que l'expéditeur (par exemple, le parent de l'acteur émetteur).

Habituellement, nous pouvons définir le deuxième paramètre surnull ouActorRef.noSender(), car nous n’attendons pas de réponse. 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();


Lorsque vous demandez une réponse à un acteur, un objetCompletionStage est renvoyé, le traitement reste donc non bloquant.

Un fait très important auquel nous devons prêter attention est la gestion des erreurs initiée par l'acteur qui répondra. To return a Future object that will contain the exception we must send a Status.Failure message to the sender actor.

Cela ne se fait pas automatiquement lorsqu'un acteur lève une exception lors du traitement d'un message et que l'appel deask() expirera et qu'aucune référence à l'exception ne sera vue dans les journaux:

@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();
}

Nous avons également la méthodeforward() qui est similaire àtell(). La différence réside dans le fait que l'expéditeur d'origine du message est conservé lors de l'envoi du message. Par conséquent, l'acteur qui transmet le message agit uniquement en tant qu'acteur intermédiaire:

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

5.2. Recevoir des messages

Each actor will implement the createReceive() method, qui gère tous les messages entrants. LereceiveBuilder() agit comme une instruction switch, essayant de faire correspondre le message reçu au type de messages défini:

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. Tuer un acteur

Lorsque nous avons fini d'utiliser un acteurwe can stop it by calling the stop() method de l'interfaceActorRefFactory:

system.stop(myActorRef);

Nous pouvons utiliser cette méthode pour mettre fin à tout acteur enfant ou à l'acteur lui-même. Il est important de noter que l’arrêt se fait de manière asynchrone et que lescurrent message processing will finish avant l’interruption de l’acteur. No more incoming messages will be accepted in the actor mailbox.

Parstopping a parent actor,we’ll also send a kill signal to all of the child actors qui ont été générés par lui.

Lorsque nous n’avons plus besoin du système d’acteurs, nous pouvons le terminer pour libérer toutes les ressources et éviter les fuites de mémoire:

Future terminateResponse = system.terminate();

Cela arrêtera les acteurs gardiens du système, d’où tous les acteurs définis dans ce système Akka.

We could also send a PoisonPill message à tout acteur que nous voulons tuer:

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

Le messagePoisonPill sera reçu par l'acteur comme tout autre message et placé dans la file d'attente. The actor will process all the messages until it gets to the PoisonPill one. Alors seulement, l'acteur commencera le processus de résiliation.

Un autre message spécial utilisé pour tuer un acteur est le messageKill. Contrairement auxPoisonPill,, l'acteur lancera unActorKilledException lors du traitement de ce message:

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

7. Conclusion

Dans cet article, nous avons présenté les bases du framework Akka. Nous avons montré comment définir les acteurs, comment ils communiquent entre eux et comment les terminer.

Nous conclurons par quelques bonnes pratiques lorsque vous travaillez avec Akka:

  • utiliseztell() au lieu deask() lorsque les performances sont un problème

  • lors de l'utilisation deask(), nous devons toujours gérer les exceptions en envoyant un messageFailure

  • les acteurs ne doivent partager aucun état mutable

  • un acteur ne doit pas être déclaré dans un autre acteur

  • actors aren’t stopped automatically lorsqu'ils ne sont plus référencés. Nous devons détruire explicitement un acteur lorsque nous n'en avons plus besoin pour éviter les fuites de mémoire

  • messages utilisés par les acteursshould always be immutable

Comme toujours, le code source de l'article est disponibleover on GitHub.