- Learn different ways of creating actors in Akka
- Learn how to exchange messages between actors
- Get familiar with common master/workers pattern
There are two different ways of creating actors in Akka.
- Actors are created by
ActorSystem. If one actor requires reference to another actor, the reference is passed as a constructor argument. - Actors are created by another actor. Created actors are children of the actor that created them.
The first approach, where ActorSystem is used to create actors, is presented below
ActorSystem system = ActorSystem.create("systemName");
ActorRef someActor = system.actorOf(Props.create(SomeActor.class), "actorName");
Instantiation of SomeActor class is performed by Props.create method. Example presented above assumes that SomeActor class has no-arguments constructor. However, Props.create method can also instantiate actors which require arguments passed to constructor.
Let's assume there is another actor defined by AnotherActor class. It has one constructor with single argument of type String. Following code presents instantiation of AnotherActor
ActorRef anotherActor = system.actorOf(Props.create(AnotherActor.class, "ConstructorArgument"), "anotherActorName");
It is possible to pass any object as a constructor argument, e.g. a reference to actor
ActorRef someActor = system.actorOf(Props.create(SomeActor.class), "actorName");
ActorRef anotherActor = system.actorOf(Props.create(AnotherActor.class, someActor), "anotherActorName");
AnotherActor class can store reference to SomeActor in a field and then send messages to this actor.
The biggest disadvantage of presented approach is that all actors are created in a single place using ActorSystem. In many complex scenarios such approach is not convenient.
Second approach allows actors to create child actors directly. Creation of child actors should be implemented in constructor of parent actor
public SomeActor() {
getContext().actorOf(Props.create(AnotherActor.class), "anotherActorName");
}
In example presented above SomeActor became a parent of AnotherActor. In order to obtain reference to children following code fragment should be used
Iterable<ActorRef> children = getContext().children();
Because SomeActor has exactly one child, the reference to it can be obtained in the following way
ActorRef anotherActor = children.iterator().next();
In order to send a message to particular actor method tell should be executed on the ActorRef object representing that actor. Method tell takes two parameters. The first one is the message to be sent and can be of any type. Second parameter is of type ActorRef. Its purpose is to provide information of who is the sender of the message.
When sending messages directly from actor system no actor can be provided as a sender. Instead, construction ActorRef.noSender() should be used
someActor.tell("Message", ActorRef.noSender());
However, when sending a message from one actor to another, the information about sender can be included. There are two important methods defined in ActorRef class
getSelf- returns a reference to current actor. MethodgetSelfhas to be used instead ofthisbecausethisvariable points to actor object (of typeUntypedActor) rather than to actor reference (of typeActorRef).getSender- returns a reference to the actor who sent received message
When sending a message to someActor from another actor following code can be used
someActor.tell("How are you?", getSelf());
Now someActor can determine sender of that message and reply directly
getSender().tell("Excellent!", getSelf());
Exercise 1
The goal of this exercise is to exchange question and answer between two actors.
There are two actors - sender and receiver. The communication is initiated directly from main method be sending Start message to sender. When sender receives Start message it sends a Question message to receiver. Receiver receives Question from sender and replies to it with an Answer message. When sender receives Answer message, it prints its content to console and shuts down whole system.
Classes representing messages are already implemented. The remaining steps are
Receiverclass
- Receiver processes only messages of type
Question. - Received question is printed to console using
infomethod from providedLoggingAdapterlog object. - Answer to the question is created using
Answerclass. getSendermethod should be used to obtain reference to the sender.tellmethod on the sender reference should be used to send answer.
Senderclass
- Sender contains one-argument constructor which takes
ActorRefobject. Value passed to the constructor should be saved inreceiverfield. - Sender processes messages of type
StartandAnswer. - When sender receives message of type
Startit sends message of typeQuestionto receiver. Instance ofQuestionmessage should be created, thentellmethod onreceiverfield should be used to send that message. A reference to itself obtained fromgetSelfmethod should be passed as a second argument oftellmethod. - When sender receives message of type
Answerit should print the text of received answer and then shut down the system. To shut down the system following code can be usedgetContext().system().shutdown().
Mainclass
Receiveractor should be instantiated and named"receiver".Senderactor should be instantiated and named"sender". Receiver object should be passed to the sender using methodProps.createwith multiple arguments.- Message
Startshould be sent to sender usingtellmethod. As a second parameter provideActorRef.noSender().
Exercise 2
This exercise is equivalent to the previous one. The only difference is the way of creating receiver actor. In this exercise receiver should be created directly from sender actor.
Receiverclass
- Should be copied from previous exercise.
Senderclass
- Sender contains no-arguments constructor in which child actor of type
Receiveris instantiated. MethodactorOfdefined inActorContextobject (context can be obtained usinggetContextmethod) should be used to instantiate child actor receiver. - Sender processes messages of type
StartandAnswer. - When sender receives message of type
Startit sends message of typeQuestionto receiver. Instance ofQuestionmessage should be created. To get reference to receiver actor methods chaingetContext().children().iterator().next()should be invoked. Having reference to receiver, question should be sent usingtellmethod. A reference to itself obtained fromgetSelfmethod should be passed as a second argument oftellmethod. - When sender receives message of type
Answerit should print the text of received answer and then shut down the system in the same way as in previous exercise.
Mainclass
Senderactor should be instantiated and named"sender".- Message
Startshould be sent to sender usingtellmethod. As a second parameter provideActorRef.noSender().
Exercise 3
The purpose of this exercise is to demonstrate how Akka can be used to distribute large set of data among multiple actors to perform computation simultaneously.
The problem to solve is finding prime numbers in large interval of numbers. Architecture assumes one actor called master and multiple actors called workers. Master is responsible for creating workers, distributing data among them and finally collecting partial results from each worker and merging them. Each worker receives a smaller interval and is responsible for searching all prime numbers in that interval. Once the interval is analysed, worker sends results to master. There are only two types of messages - input and output.
To simplify the exercise several components are already implemented
PrimeCheckerclass contains logic for checking whether a number is prime number.Intervalclass represents set of numbers bounded inclusively from both sides.Intervalcontains methoddividewhich can be very useful when dividing large set of data into smaller chunks for each worker.Inputmessage contains interval of numbers to process.Outputmessage contains set of prime numbers found in interval.
To finish this exercise implement following steps
Workerclass
- Worker receives only messages of type
Input. - Worker iterates through all numbers from given interval and if the number is a prime number worker stores it in a variable of type
Set<Long>. - Once interval is analysed, worker sends
Outputmessage with found prime numbers back to the actor who sentInputmessage (back to master).
Masterclass
- Master first instantiates workers. The amount of workers is given in constructor's
numberOfWorkersargument. To create actors directly fromMasteruse technique known from previous exercise. - Master receives two types of messages -
Input(from actor system) andOutput(from workers). - For
Inputmessage master divides large interval into smaller intervals depending on the number of workers. Every worker should receive exactly one interval wrapped intoInputmessage. - For
Outputmessage master collects received prime numbers and stores them in a field variable of typeSet<Long>. When messages from all workers reach the master, the number of found prime numbers should be printed to console and system should be shut down. To determine whether all workers have sent output messages a common integer counter should be used.
PrimesFinderclass
- Primes finder creates
ActorSystemand one actor of typeMaster. VariablenumberOfWorkersshould be passed to master actor. - Primes finder sends
Inputmessage with interval to master.
- Measure processing time depending on number of workers. Compare application performance for 1, 2, 4, 10, 100, 1000, 10000, 100000 of workers. Draw your own conclusions.