📜 ⬆️ ⬇️

Overview Typesafe Stack 2.0 and an introduction to the model of actors on the example of Akka 2.0

image

Typesafe Stack is a modern software platform that provides the ability to create highly scalable software systems in Java and Scala. The stack runs on the JVM, includes the Akka 2.0 and Playframework 2.0 frameworks. The core of the platform, which provides virtually unlimited scalability of the developed system, is the Akka library, which implements multitasking based on the model of actors.

Actor model

A rigorous definition of the model of actors, as well as the history of its origin and fundamental concepts can be found in Wikipedia . In short, the model of actors proceeds from the philosophy that everything around is actors. This is similar to the philosophy of object-oriented programming with the only difference that in the model of actors, calculations essentially coincide in time, as in object-oriented programming, as a rule, programs are executed sequentially. In the actor model, there is no shared state, and data is exchanged using the mechanism for transferring messages from one actor to another. Each actor has a queue of incoming messages that are processed by it sequentially. Due to the lack of a shared state and sequential processing of messages, parallelization of computations in programs based on the model of actors is greatly simplified, and the reliability of such programs increases.
')
Akka

As written above, Akka implements the model of actors on the JVM. Actors are also called lightweight threads (green threads). Such streams are really lightweight, their number can reach ~ 2.7 million per 1 GB of RAM, and the number of transmitted messages is more than 20 million / s on a single node. It is possible to organize the interaction of actors through the network, which provides almost unlimited scalability of the developed application. More information about the features of Akka can be found on the official website .

Typesafe and Akka usage example

Original: here

Starting to use Akka as part of the Typesafe Stack is quite simple. The configuration process is reduced to downloading the stack distribution kit and installing it . Although it is possible to use both Java and Scala for development, the use of the latter makes the program especially slim and easy to read and understand, so we will use Scala later in the example.

Formulation of the problem

In this example, we will use the actors to calculate the value of the PI number. This operation uses a processor intensively, so Akka will allow parallel computing to be scalable on a multi-core machine. The algorithm used is well parallelized because Each of the calculations is independent, which is perfectly projected onto the model of actors. The formula for the calculation is as follows:

image

The main actor will divide this series into components and send each of the members to the auxiliary actor for calculation (one auxiliary actor for each member of the series). Upon completion of the calculation, each of the auxiliary actors will return the result to the main actor. After receiving the results of all calculations, the main actor will form the answer.

Creating a project

First, you need to create a project from the giter8 template (Typesafe Stack must be installed):
c:\temp>g8 typesafehub/akka-scala-sbt

Akka 2.0 Project Using Scala and sbt

organization [org.example]: akka.tutorial
package [org.example]: akka.tutorial.first.scala
name [Akka Project In Scala]: PI Calculator
akka_version [2.0]:
version [0.1-SNAPSHOT]:

Applied typesafehub/akka-scala-sbt.g8 in pi-calculator

Now that the project has been created, go to the project folder:
c:\temp>cd pi-calculator

Start writing code

First of all, you need to create a ./src/main/scala/akka/tutorial/first/scala/ file in the appropriate folder ( ./src/main/scala/akka/tutorial/first/scala/ ) and import the necessary libraries:
import akka.actor._
import akka.routing.RoundRobinRouter
import akka.util.Duration
import akka.util.duration._

Creating message templates

We clarify the architecture of our application briefly described above. There is one Master actor that creates a multitude of Worker actors and initializes the calculation. To do this, he divides the entire task into small operations and sends these operations for the Worker to perform to the actors. After performing the operations, the Worker actors return the results for the aggregation. After the calculation is completed, the Master actor sends the result Listener actor, which displays it on the screen.

Based on this description, we will create messages that will be transmitted in the system:

Messages sent to actors must be immutable in order to exclude a common mutable state. In Scala, case classes are great for this. We will also create a common basic trait for messages, which we denote as sealed in order to avoid the uncontrolled addition of messages:
 sealed trait PiMessage
 case object Calculate extends PiMessage
 case class Work (start: Int, nrOfElements: Int) extends PiMessage
 case class Result (value: Double) extends PiMessage
 case class PiApproximation (pi: Double, duration: Duration)

Creating the Worker Actor

Now we can create a Worker actor. This is done by mixing the Actor trait and defining the receive method. This method is a handler for incoming messages:
 class Worker extends Actor {

   // calculatePiFor ...

   def receive = {
     case Work (start, nrOfElements) ⇒
       sender!  Result (calculatePiFor (start, nrOfElements)) // perform the work
   }
 }

As you can see, we added the Work message handler, which creates a Result response message after the calculatePiFor operation and sends it back to the sender. Now let's implement calculatePiFor :
 def calculatePiFor (start: Int, nrOfElements: Int): Double = {
   var acc = 0.0
   for (i ← start until (start + nrOfElements))
     acc + = 4.0 * (1 - (i% 2) * 2) / (2 * i + 1)
   acc
 }


Creating a Master Actor

Master actor is a bit more complicated. a cyclic router is created in its constructor to facilitate the distribution of operations between Worker actors:
 val workerRouter = context.actorOf (
   Props [Worker] .withRouter (RoundRobinRouter (nrOfWorkers)), name = "workerRouter")

Now we will write Master Actor. This actor is created with the following parameters:

So we have the following code:
 class Master (nrOfWorkers: Int, 
   nrOfMessages: Int, 
   nrOfElements: Int, 
   listener: ActorRef) extends Actor {

   var pi: Double = _
   var nrOfResults: Int = _
   val start: Long = System.currentTimeMillis

   val workerRouter = context.actorOf (
     Props [Worker] .withRouter (RoundRobinRouter (nrOfWorkers)), name = "workerRouter")

   def receive = {
     // handle messages ...
   }

 }

In addition to the parameters described above, the ActorRef object, which is a reference to the Listener actor, is passed to the Master actor. It should be noted that the transfer of messages between actors is always done through such links.

But that's not all, because we did not implement message handlers, which we will write now:
 def receive = {
   case Calculate ⇒
     for (i ← 0 until nrOfMessages) 
       workerRouter!  Work (i * nrOfElements, nrOfElements)
   case Result (value) ⇒
     pi + = value
     nrOfResults + = 1
     if (nrOfResults == nrOfMessages) {
       // Send the result to the listener
       listener!  PiApproximation (pi, duration = (System.currentTimeMillis - start) .millis)
       // Stops this actor and children
       context.stop (self)
     }
 }

In general, the code should be clear, it should only be noted that after completing the calculation and sending the result to print, the Master actor stops using the context.stop(self) command.

Create Listener Actor

The implementation of this actor is quite simple. It accepts the PiApproximation message from the Master actor, prints the result and stops the actor system:
 class Listener extends Actor {
   def receive = {
     case PiApproximation (pi, duration) ⇒
       println ("\ n \ tPi approximation: \ t \ t% s \ n \ tCalculation time: \ t% s"
         .format (pi, duration))
       context.system.shutdown ()
   }
 }


Writing an Application Object

Now it remains only to write an application object and our program is ready:
 object Pi extends App {

   calculate (nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000)

   // actors and messages ...

   def calculate (nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) {
     // Create an Akka system
     val system = ActorSystem ("PiSystem")

     // create the result listener 
     // shutdown the system
     val listener = system.actorOf (Props [Listener], name = "listener")

     // create the master
     val master = system.actorOf (Props (new Master (
       nrOfWorkers, nrOfMessages, nrOfElements, listener)),
       name = "master")

     // start the calculation
     master!  Calculate

   }
 }


Application launch

The easiest way to run an application from sbt. To do this, go to the folder with the application and type the command sbt . Then we execute the following sequence of commands:
c:\temp\pi-calculator>sbt
[info] Loading project definition from C:\temp\pi-calculator\project
[info] Set current project to PI Calculator (in build file:/C:/temp/pi-calculator/)
> compile
[success] Total time: 0 s, completed 20.03.2012 16:33:03

> run
[info] Running akka.tutorial.first.scala.Pi

Pi approximation: 3.1415926435897883
Calculation time: 423 milliseconds
[success] Total time: 1 s, completed 20.03.2012 16:33:25

If you reduce the number of actors to 1, the result will change:
> run
[info] Running akka.tutorial.first.scala.Pi

Pi approximation: 3.1415926435897883
Calculation time: 1160 milliseconds
[success] Total time: 2 s, completed 20.03.2012 16:35:16

If you increase to 8, the result will be as follows:
> run
[info] Running akka.tutorial.first.scala.Pi

Pi approximation: 3.1415926435897905
Calculation time: 388 milliseconds
[success] Total time: 1 s, completed 20.03.2012 16:36:55

The result is predictable, given that the tests were conducted on a 4-nuclear machine. So increasing the number of actors to 16 gives almost no performance gain:
> run
[info] Running akka.tutorial.first.scala.Pi

Pi approximation: 3.141592643589789
Calculation time: 372 milliseconds
[success] Total time: 1 s, completed 20.03.2012 16:40:04

The post about Play 2.0 is planned soon. Good luck!

Source: https://habr.com/ru/post/140368/


All Articles