akka entry series - 5. Routing messages

Keywords: Java Concurrent Programming IoT Akka

5. Routing messages

In the previous example, we created and used a single actor, and an actor can only process one message at the same time, which does not give play to the advantages of akka parallel computing. We hope to process messages in parallel, just like kafka's consumer. This requires the use of routing to akka.

Routing is the same as the conventional understanding. When a message arrives, it can be distributed to the actor s after routing according to a certain strategy. There are two types of routing in akka, pool routing and group routing.

  • Pool Routing: from the name, a pile of actor s are in a pool.
  • Group Routing: according to the name, a pile of actor s belong to a group.

What's the difference between the two?

In a non cluster environment or a single node environment, there is no difference between the two, and both can complete the load / distribution of messages** However: * * pool routing can only be routed within a single ActorSystem, while group routing can be routed across actorsystems within the cluster. (it can be understood that a pool has physical boundaries like a pool, while a group can cross regions like a group) as shown in the following figure.

  • pool router:

  • group router:

Moreover, the implementation methods of the two routes are also different.

pool router

Create a pool route as follows:

    // Configuration of actor
    BehaviorConfig filterConfig = new BehaviorConfig("power > 2000");
    // Create filter behavior
    Behavior<IDeviceMessage> filterBehavior = FilterActor.create(filterConfig, null);
    // Encapsulate filter behavior into pool router behavior
    Behavior<IDeviceMessage> filterPoolBehavior = Routers.pool(5, filterBehavior.narrow()).withRoundRobinRouting();
    // Instantiate filter pool router
    ActorRef<IDeviceMessage> filterPoolRouterRef = context.spawn(filterPoolBehavior, "FilterPoolRouter");

    DevicePropertyMessage message = DevicePropertyMessage.builder()
            .thingId("D007")
            .current(3.58)
            .voltage(380.00)
            .build();
    // Send message to pool router
    for (int i = 0; i < 10; i++) {
        filterPoolRouterRef.tell(message);
    }

There are three main steps:

  1. First create the Behavior of the basic actor in the pool. Here, we create its pool actor for FilterActor.
  2. Encapsulate the Behavior of the basic actor into the Behavior of the pool actor through the routes. Pool () method. Here, you can configure the number of sub actors of each pool route and the routing policy (supporting polling, randomization, broadcasting and consistent hashing mechanisms).
  3. Instantiate the pool actor through the spawn() method.

In this way, when we send messages to the pool route, the pool route will poll and send messages to the five internal sub actor s according to the RoundRobin policy configured here.

In the above example, we send 10 messages to the pool router. There are five sub actors under the pool. When the FilterActor receives the message, we print out the actor path, thread id and objectId of the current actor in turn. The results are as follows:

=== actor system started ===
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$b 	 threadId=28 	 objectId=1644765223
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$c 	 threadId=29 	 objectId=710798201
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$e 	 threadId=31 	 objectId=1877431981
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$d 	 threadId=30 	 objectId=1124465799
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$a 	 threadId=23 	 objectId=610546090
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$c 	 threadId=29 	 objectId=710798201
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$e 	 threadId=31 	 objectId=1877431981
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$b 	 threadId=28 	 objectId=1644765223
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$d 	 threadId=30 	 objectId=1124465799
[filter actor] Message received: actor path=akka://local-example/user/FilterPoolRouter/$a 	 threadId=23 	 objectId=610546090

The following points can be found:

  • The child actor is represented as FilterPoolRouter/ a , F i l t e r P o o l R o u t e r / a, FilterPoolRouter/ a. FilterPoolRouter/b, where FilterPoolRouter is the name of pool actor. It can be seen that each child actor is at the next level of pool actor.
  • Messages will be processed by different threads and actor classes (this involves akka's message dispatcher mechanism. If you are interested, please refer to the Dispatchers section of the official document)

group router

Create a group route as follows:

    // Configuration of actor
    BehaviorConfig filterConfig = new BehaviorConfig("power > 2000");
    // Create filter behavior
    Behavior<IDeviceMessage> filterBehavior = FilterActor.create(filterConfig, null);
    // Instantiate filter actor
    ActorRef<IDeviceMessage> filterActorRef = context.spawn(filterBehavior, "FilterActor");
    
    // Register with a key in the filter Pool Router
    ServiceKey<IDeviceMessage> filterServiceKey = ServiceKey.create(IDeviceMessage.class, "FilterGroupRouterKey");
    context.getSystem().receptionist().tell(Receptionist.register(filterServiceKey, filterActorRef.narrow()));

There are three main steps:

  1. Instantiate a common actor and obtain its ActorRef
  2. Create a service key for the group router, which is the unique ID of the group router
  3. Register the above actor to the key through receptionist().tell()

In this way, we have registered an actor to the group routing. How can I send messages to this group?

 //Create a key with the same name
 ServiceKey<IDeviceMessage> filterServiceKey = ServiceKey.create(IDeviceMessage.class, "FilterGroupRouterKey");
 //Get ActorRef of group route
 Behavior<IDeviceMessage> filterGroupBehavior = Routers.group(filterServiceKey).withRoundRobinRouting();
 ActorRef<IDeviceMessage> filterGroupRouterRef = context.spawn(filterGroupBehavior, "FilterGroupRouter");
 // Send message to group router
 filterGroupRouterRef.tell(message);

It can also be divided into three steps:

  1. First create a ServiceKey with the same name, or directly use the created ServiceKey
  2. Get the Behavior of the group router through the routes. Group() method, and specify the routing policy at the same time
  3. Use context.spawn() to create the actor of the group router, so you can get the ActorRef of the group router and send messages.

Similarly, we register five actors to the group router, and then send 10 messages to the group router to see the actor path, thread id and objectId. The results are as follows:

[filter actor] Message received: actor path=akka://local-example/user/FilterActor3 	 threadId=29 	 objectId=868900288
[filter actor] Message received: actor path=akka://local-example/user/FilterActor2 	 threadId=28 	 objectId=1697801780
[filter actor] Message received: actor path=akka://local-example/user/FilterActor4 	 threadId=30 	 objectId=1650033925
[filter actor] Message received: actor path=akka://local-example/user/FilterActor1 	 threadId=23 	 objectId=1977819714
[filter actor] Message received: actor path=akka://local-example/user/FilterActor5 	 threadId=31 	 objectId=403997215
[filter actor] Message received: actor path=akka://local-example/user/FilterActor3 	 threadId=29 	 objectId=868900288
[filter actor] Message received: actor path=akka://local-example/user/FilterActor4 	 threadId=30 	 objectId=1650033925
[filter actor] Message received: actor path=akka://local-example/user/FilterActor2 	 threadId=28 	 objectId=1697801780
[filter actor] Message received: actor path=akka://local-example/user/FilterActor1 	 threadId=23 	 objectId=1977819714
[filter actor] Message received: actor path=akka://local-example/user/FilterActor5 	 threadId=31 	 objectId=403997215

It can be found that:

  • Messages are still distributed evenly across the five actor s, processed by different classes and threads
  • It is worth noting that the actor path of the actor is different from the pool actor. Each actor is not below the level of the group actor, that is, they are not child actors of the group actor.

Here, we can learn the hierarchical logic of actors, as shown in the figure below. In the actor system, each actor is hierarchical, and this level is reflected in the actor path. All the actors we create through the spawn() method will be under user, and the child actors will be managed by the parent actor.

**Note: * * in the typed version, actor path is not very useful. In the classic version, we can obtain ActorRef through actor path.

To sum up, the logic of the pool router is the management mode, which tells it the Behavior (i.e. Behavior), routing rules and the number of sub actors to be routed. It will create a pile of sub actors to distribute messages. The logic of the group router is the registration mode. Give the router a key, then register the actor on the key, and then check the registry to realize message distribution. In addition, a very important difference is that the pool router can only be implemented under the current actor system, while the group router can implement routing within the cluster across the actor system. It will be further introduced later when implementing the cluster.

Posted by dmb on Mon, 27 Sep 2021 22:14:44 -0700