Inleiding tot Akka-acteurs in Java

1. Inleiding

Akka is een open-sourcebibliotheek waarmee u eenvoudig gelijktijdige en gedistribueerde applicaties kunt ontwikkelen met behulp van Java of Scala door gebruik te maken van het Actormodel.

In deze tutorial we zullen de basisfuncties presenteren, zoals het definiëren van acteurs, hoe ze communiceren en hoe we ze kunnen doden. In de laatste opmerkingen zullen we ook enkele best practices noteren bij het werken met Akka.

2. Het actormodel

Het Actormodel is niet nieuw voor de informatica-gemeenschap. Het werd voor het eerst geïntroduceerd door Carl Eddie Hewitt in 1973, als een theoretisch model voor het omgaan met gelijktijdige berekeningen.

Het begon zijn praktische toepasbaarheid te tonen toen de software-industrie de valkuilen van het implementeren van gelijktijdige en gedistribueerde applicaties begon te beseffen.

Een actor vertegenwoordigt een onafhankelijke rekeneenheid. Enkele belangrijke kenmerken zijn:

  • een actor vat zijn toestand en een deel van de applicatielogica samen
  • acteurs communiceren alleen via asynchrone berichten en nooit via directe methodeaanroepen
  • elke actor heeft een uniek adres en een mailbox waarin andere acteurs berichten kunnen bezorgen
  • de actor zal alle berichten in de mailbox in opeenvolgende volgorde verwerken (de standaardimplementatie van de mailbox is een FIFO-wachtrij)
  • het actor-systeem is georganiseerd in een boomachtige hiërarchie
  • een acteur kan andere acteurs creëren, kan berichten naar elke andere acteur sturen en zichzelf stoppen of elke acteur is gemaakt

2.1. Voordelen

Het ontwikkelen van gelijktijdige applicaties is moeilijk omdat we te maken hebben met synchronisatie, vergrendelingen en gedeeld geheugen. Door Akka-acteurs te gebruiken, kunnen we gemakkelijk asynchrone code schrijven zonder de noodzaak van vergrendelingen en synchronisatie.

Een van de voordelen van het gebruik van bericht in plaats van methodeaanroepen is dat de afzender-thread blokkeert niet om te wachten op een retourwaarde wanneer het een bericht naar een andere actor stuurt. De ontvangende actor reageert met het resultaat door een antwoordbericht naar de afzender te sturen.

Een ander groot voordeel van het gebruik van berichten is dat we ons geen zorgen hoeven te maken over synchronisatie in een omgeving met meerdere threads. Dit komt door het feit dat alle berichten worden opeenvolgend verwerkt.

Een ander voordeel van het Akka-actormodel is het afhandelen van fouten. Door de actoren in een hiërarchie te organiseren, kan elke actor zijn ouder op de hoogte stellen van de mislukking, zodat hij dienovereenkomstig kan handelen. De ouderacteur kan beslissen om de kindacteurs te stoppen of opnieuw te starten.

3. Installatie

Om te profiteren van de Akka-acteurs, moeten we de volgende afhankelijkheid van Maven Central toevoegen:

 com.typesafe.akka akka-actor_2.12 2.5.11 

4. Een acteur creëren

Zoals gezegd zijn de actoren gedefinieerd in een hiërarchisch systeem. Alle actoren die een gemeenschappelijke configuratie delen, worden gedefinieerd door een ActorSystem.

Voor nu definiëren we eenvoudigweg een ActorSystem met de standaardconfiguratie en een aangepaste naam:

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

Hoewel we nog geen acteurs hebben gemaakt, bevat het systeem al 3 hoofdactoren:

  • de root guardian-actor met het adres "/" dat, zoals de naam aangeeft, de root van de actor-systeemhiërarchie vertegenwoordigt
  • de user guardian actor met het adres “/ user”. Dit zal de ouder zijn van alle actoren die we definiëren
  • de systeemvoogd-actor met het adres “/ systeem”. Dit zal de ouder zijn voor alle actoren die intern door het Akka-systeem zijn gedefinieerd

Elke Akka-acteur zal het AbstractActor abstracte klasse en implementeer het createReceive () methode voor het afhandelen van de inkomende berichten van andere actoren:

openbare klasse MyActor breidt AbstractActor uit {openbaar ontvangen createReceive () {terugkeer ontvangenBuilder (). build (); }}

Dit is de meest elementaire actor die we kunnen creëren. Het kan berichten van andere actoren ontvangen en zal deze verwijderen omdat er geen overeenkomende berichtpatronen zijn gedefinieerd in het OntvangBuilder. We zullen het later in dit artikel hebben over het matchen van berichtpatronen.

Nu we onze eerste acteur hebben gemaakt, moeten we deze opnemen in de ActorSystem:

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

4.1. Actor configuratie

De Rekwisieten class bevat de actor-configuratie. We kunnen zaken configureren als de dispatcher, de mailbox of implementatieconfiguratie. Deze klasse is onveranderlijk, dus thread-safe, dus het kan worden gedeeld bij het maken van nieuwe acteurs.

Het wordt ten zeerste aanbevolen en wordt beschouwd als een best-practice om de fabrieksmethoden in het actor-object te definiëren die het maken van het Rekwisieten voorwerp.

Om een ​​voorbeeld te geven, laten we een actor definiëren die wat tekst zal verwerken. De acteur krijgt een Draad object waarop het de verwerking zal uitvoeren:

openbare klasse ReadingActor breidt AbstractActor {private String-tekst uit; openbare statische rekwisieten rekwisieten (String text) {return Props.create (ReadingActor.class, text); } // ...}

Om een ​​instantie van dit type actor te maken, gebruiken we gewoon de rekwisieten() fabrieksmethode om de Draad argument voor de constructor:

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

Nu we weten hoe we een actor moeten definiëren, laten we eens kijken hoe ze communiceren binnen het actor-systeem.

5. Actor Berichten

Om met elkaar te communiceren, kunnen de acteurs berichten van elke andere actor in het systeem verzenden en ontvangen. Deze berichten kunnen elk type object zijn met de voorwaarde dat het onveranderlijk is.

Het is een best practice om de berichten in de actorklasse te definiëren. Dit helpt bij het schrijven van code die gemakkelijk te begrijpen is en weet welke berichten een acteur kan verwerken.

5.1. Berichten verzenden

Binnen de Akka-actor worden systeemberichten verzonden met behulp van methoden:

  • vertellen()
  • vragen()
  • vooruit()

Als we een bericht willen sturen en geen reactie verwachten, kunnen we de vertellen() methode. Dit is de meest efficiënte methode vanuit een prestatieperspectief:

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

De eerste parameter vertegenwoordigt het bericht dat we naar het actor-adres sturen readingActorRef.

De tweede parameter specificeert wie de afzender is. Dit is handig wanneer de actor die het bericht ontvangt een antwoord moet sturen naar een andere actor dan de afzender (bijvoorbeeld de ouder van de verzendende actor).

Meestal kunnen we de tweede parameter instellen op nul of ActorRef.noSender (), omdat we geen antwoord verwachten. Als we een reactie van een acteur nodig hebben, kunnen we de vragen() methode:

CompletableFuture future = ask (wordCounterActorRef, nieuwe WordCounterActor.CountWords (regel), 1000) .toCompletableFuture ();

Bij het vragen om een ​​reactie van een acteur a Voltooiingsfase object wordt geretourneerd, dus de verwerking blijft niet-blokkerend.

Een zeer belangrijk feit waar we op moeten letten, is het afhandelen van fouten, insider de actor die zal reageren. Om een Toekomst object dat de uitzondering zal bevatten, moeten we een Status. Mislukking bericht aan de afzender.

Dit wordt niet automatisch gedaan wanneer een actor een uitzondering genereert tijdens het verwerken van een bericht en de vragen() de oproep zal een time-out hebben en er zal geen verwijzing naar de uitzondering worden gezien in de logboeken:

@Override openbaar Ontvang createReceive () {retourneer ontvangBuilder () .match (CountWords.class, r -> {probeer {int numberOfWords = countWordsFromLine (r.line); getSender (). Tell (numberOfWords, getSelf ());} vangst (Exception ex) {getSender (). Tell (new akka.actor.Status.Failure (ex), getSelf ()); throw ex;}}). Build (); }

We hebben ook de vooruit() methode die vergelijkbaar is met vertellen(). Het verschil is dat de oorspronkelijke afzender van het bericht wordt behouden bij het verzenden van het bericht, dus de actor die het bericht doorstuurt, fungeert alleen als tussenpersoon:

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

5.2. Berichten ontvangen

Elke actor zal het createReceive () methode, die alle inkomende berichten afhandelt. De ontvangenBuilder () gedraagt ​​zich als een switch-instructie en probeert het ontvangen bericht af te stemmen op het type berichten dat is gedefinieerd:

openbaar Ontvang createReceive () {retourneer ontvangBuilder (). matchEquals ("printit", p -> {System.out.println ("Het adres van deze actor is:" + getSelf ());}). build (); }

Bij ontvangst wordt een bericht in een FIFO-wachtrij geplaatst, zodat de berichten opeenvolgend worden afgehandeld.

6. Een acteur vermoorden

Toen we klaar waren met het gebruik van een acteur we kunnen het stoppen door de hou op() methode van de ActorRefFactory koppel:

system.stop (myActorRef);

We kunnen deze methode gebruiken om elke kindacteur of de acteur zelf te beëindigen. Het is belangrijk op te merken dat het stoppen asynchroon wordt uitgevoerd en dat de de huidige berichtverwerking wordt voltooid voordat de acteur wordt beëindigd. Er worden geen inkomende berichten meer geaccepteerd in de actor-mailbox.

Door het stoppen van een ouderacteur, we sturen ook een kill-signaal naar alle kindacteurs die erdoor werden voortgebracht.

Als we het actor-systeem niet meer nodig hebben, kunnen we het beëindigen om alle bronnen vrij te maken en geheugenlekken te voorkomen:

Future terminateResponse = system.terminate ();

Dit zal de systeembewaker-actoren stoppen, dus alle actoren die in dit Akka-systeem zijn gedefinieerd.

We kunnen ook een Vergifpil bericht aan elke acteur die we willen vermoorden:

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

De Vergifpil bericht wordt door de acteur ontvangen zoals elk ander bericht en in de wachtrij geplaatst. De actor verwerkt alle berichten totdat deze bij het Vergifpil een. Pas dan begint de actor met het beëindigingsproces.

Een ander speciaal bericht dat wordt gebruikt om een ​​acteur te vermoorden, is de Doden bericht. In tegenstelling tot de Vergifpil, de acteur gooit een ActorKilledException bij het verwerken van dit bericht:

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

7. Conclusie

In dit artikel hebben we de basisprincipes van het Akka-framework gepresenteerd. We lieten zien hoe je acteurs definieert, hoe ze met elkaar communiceren en hoe je ze kunt beëindigen.

We sluiten af ​​met enkele praktische tips voor het werken met Akka:

  • gebruik vertellen() in plaats van vragen() wanneer prestaties een punt van zorg zijn
  • tijdens gebruik vragen() we moeten uitzonderingen altijd afhandelen door een Mislukking bericht
  • actoren mogen geen veranderlijke staat delen
  • een acteur mag niet binnen een andere acteur worden verklaard
  • acteurs worden niet automatisch gestopt wanneer er niet langer naar wordt verwezen. We moeten een actor expliciet vernietigen als we hem niet meer nodig hebben om geheugenlekken te voorkomen
  • berichten die worden gebruikt door acteurs moet altijd onveranderlijk zijn

Zoals altijd is de broncode voor het artikel beschikbaar op GitHub.