Akka Router – Router As an actor

Reading Time: 7 minutes

The point of writing this blog is to give basic knowledge of Akka-Router. How we can achieve the multithreading in Akka using akka router also. As we know in Akka “Actor Model is used for the purpose of multithreading through the actors then why we need router…?

Introduction:

Problem: A situation which across very frequently is that you have a large number of tasks and you need to compute and distribute this task across multiple actors in the “actor model”. That is all the task was given to one actor and one actor to other actors. The question is in case of large tasks how we create actors and distribute these tasks over the actors, who are going to perform this distribution?

Solution:Akka Router, Akka Provides a library that solves this problem using the routing.

The router is an actor that send messages in an efficient way to the destination actor known as a route. 

Different routers use different strategies to send or route messages or tasks. Routers can be used inside or outside of an actor. And you can manage the routes yourself or use a self-contained router actor with configuration capabilities. You can also resize dynamically under load.

As shown in fig 1.1 Actor A sends a message to the router R. R will assign these messages to one of the worker’s actors present in a router. The processing of message get started. Actor  B sends a message to router R and the router assigns this message to one of the worker actors who is free. As shown in fig 1.2 in this way the router accomplishes the concept of concurrency. As at the same time message from actor A and B processes.

Note: As shown below fig 1.3. Actor A and B also request router to process the message then router assigned to worker actor. Once the processing is done the worker actor sends the response directly to Actor A and B rather than send it to the router and then the original actor.

Fig:1.3

Types of Akka Router Actor:

1. Pool: Pool actor works as a parent actor. It creates multiple child actors known as routee i.e our worker actors told in the above section. We just need to pass a number of instances to the router for creating that number of routee. Distributes tasks over its routee and in case of any routee terminate Pool will remove this routee.

The following code shows an example of Pool Router Actor

package com.knoldus

import akka.actor.{Actor, ActorLogging}
import com.knoldus.workerProtocol.Work

class Worker extends Actor with ActorLogging {
  override def receive={
    case Work(msg) => log.info(s"I recieved work message $msg and my actorRef:${self.path.name}")
  }
  }
object workerProtocol{
  case class Work(name:String)
}
package com.knoldus

import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, PoisonPill, Props}
import com.knoldus.workerProtocol.Work

import akka.io.Udp.SO.Broadcast
import com.knoldus.Worker

class Router extends Actor with ActorLogging{
  var routees: List[ActorRef] = _

  override def preStart()={
    routees = List.fill(5){
      context.actorOf(Props[Worker])
    }
  }

  override def receive={
    case msg:Work => log.info("I m a router and i recieved a message...")
      routees(util.Random.nextInt(routees.size))forward(msg)
   }
}
object  Router extends App{
val system = ActorSystem("Router")
val router= system.actorOf(Props(classOf[Router]))
  router ! Work("hi")
  router ! Work("hello")
 system.terminate()
}

Output:

2. Group: In a group actor, the number of routee is created outside the group actor. For the purpose of distributing the task path of a particular routee need to be sent using actor Selection. In this, we not check whether the routee is terminated or not. 

The following code shows an example of Group Router Actor

package com.knoldus

import akka.actor.{Actor, ActorLogging}
import com.knoldus.workerProtocol.Work

class Worker extends Actor with ActorLogging {
  override def receive={
    case Work(msg) => log.info(s"I recieved work message $msg and my actorRef:${self.path.name}")
  }
  }
object workerProtocol{
  case class Work(name:String)
}
package com.knoldus

import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, PoisonPill, Props}
import com.knoldus.workerProtocol.Work

import akka.io.Udp.SO.Broadcast
import com.knoldus.Worker

class RouterGroup( routees : List[String])extends Actor with ActorLogging {
  override def receive = {
    case msg: Work => log.info("I m a router and i recieved a message...")
      context.actorSelection(routees(util.Random.nextInt(routees.size))) forward (msg)
  }
}

object  Router extends App{
val system = ActorSystem("Router")
  system.actorOf(Props[Worker],name = "worker1")
  system.actorOf(Props[Worker],name = "worker2")
  system.actorOf(Props[Worker],name = "worker3")

 val workers :List[String]=List("/user/worker1",
    "/user/worker2",
    "/user/worker3")
  val routerGroup= system.actorOf(Props(classOf[RouterGroup],workers))
  routerGroup ! Work()
  routerGroup ! Work() 

system.terminate()
}

Output:

Router Supervision using Pool:

Routees that are created by using pool router treated as children of the router.

As shown in below fig 1.4. If an error occurs at worker actors ‘w’ (child of a Router). For which Router R work as parent handles error i.e router works as a supervisor for the worker actors. In the same way, the router is also supervised by parent A (as the router is a child of A)

The supervision strategy is makeup using supervisorStrategy property of the Pool. By using these strategy the router has the capability of resume, restart and stop worker actor. 

Fig:1.4

There are two types of supervisorStrategy i.e OneForOneStrategy and AllForOneStrategy. If no one strategy is defined by the router then an error is directly escalating to the parent. And the parent will decide what to do about any error. It means by default supervisorStrategy is escalate.

  1. OneForOneStrategy: An error occurs in a particular child( i.e worker actor). The router or parent will perform an action on that child only(e.g resume, restart, stop).
  2. AllForOneStrategy: An error occurs in a particular child( i.e worker actor). The router or parent will perform an action on all child (e.g resume, restart, stop).

Specially Handled Message:

In Akka router, Not all but most messages sent to the router actor will forward this message according to router logic. Routing logic depends on which router we are going to use which is described in the next section “Routing Strategies. But there are some messages which have their own behavior called ‘Specially Handled Message’.

All the special messages except the broadcast message, are handled by a self-contained router actor and not by the Akka.routing.Router.
The different special messages are:

  • Broadcast Message of akka router.
  • PoisonPill Message of akka router.
  • Kill Message of akka router.

1. Broadcast Message:

Broadcast messages are messages that Router can send to all its routees at a time. The messages on each routee work independently it means it does not affect each other.

Below fig shows how exactly the broadcast message work.

Fig:1.5

An actor A send broadcast message “I like knoldus culture” to router R, R extract the message “ I like knoldus culture” and send it to all its routees. No matter how R would route message to its routees.

import akka.routing.Broadcast
router ! Broadcast("I like knoldus culture")

2. PoisonPill Message:

In the general, router, which normally passes messages to routees, it is important to note that PoisonPill messages are processed by the router only. PoisonPill messages sent to a router will not be sent on to routees.

When an actor sends PoisonPill message to router, the router gets to stop and all its routees get affected i.e routees will process their current message and stop. In this case various messages remain unprocessed in the mailbox.

Note: In normal when an actor sends PoisonPill message to another actor, the actor processes all its messages in the mailbox he get before the PoisonPill messages and then stops the actor. 

To achieve this in case of router i.e u wish that all routees process its all messages currently present in the mailbox, Actor needs to send PoisonPill message by wrapping into broadcast instead of directly sending as PoisonPill.

import akka.actor.PoisonPill
import akka.routing.Broadcast
router ! Broadcast(PoisonPill)

Note: When an actor sends a Broadcast(PoisonPill) message, Routees that are children of router and routee which do not also process all its messages in the mailbox he get before the PoisonPill messages and then stop. 

3. Kill Message:

When a Kill message is sent to a router the router processes the message internally and does not send it on to its routees. The router will throw an ActorKilledException and fail. It will then be either resumed, restarted or terminated, depending on how it is supervised.

The difference in Broadcast(PoisonPill) and Broadcast(Kill) message is, Only Routees that are children of router would get a stop. 

import akka.actor.Kill
router ! Kill

Routing Strategies:

Round-Robin Pool And Group :

In this routing strategy, all messages routed to its routee using round-robin logic. Following fig shows how round robin works 

ScatterGatherFirstCompletedPool And Group:

In this strategy the sender sends messages to the router (i.e akka router) and the router will forward this message to all its routes i.e actor1, actor2 and actor3 as shown in fig and route who first replies to that message will be taken as response and send to the sender. All other responses are ignored.

TailChoppingPool And Group:

This routing strategy works the same as the ScatterGatherFirstCompletedPool the only difference is the message sent to all its routes one by one after a particular dealy. The route selection is random.  It send the message to a routee and waits for the response if no response is received within a specified delay router will send this message to another routee. If again no response from another routee within a specified time, the process of sending messages to another routee will continue.  It waits for the first reply from any of the routees, and forwards it back to the original sender. Other replies are discarded. If no reply is received after a specified interval, a timeout failure is generated.

BrodcastPool And Group :

As we already know in all above strategies message is sent to a particular routee but BroadcastPool and Group routing strategy have special behavior. In this routing, a single message will be sent to all routees.

RandomPool And Group:

Routes the message to one of the routees at random.

References:

If you want to learn more about akka routing , Click here for Akka http routing.