Introduction Reactive System. Akka Actor Model & Akka Modules Fundamental.
Oluwaseyi Otun
Lead Backend Software Engineer ( Scala, Python, Java, Kafka, ZIO, Akka)
With the explosion of mobile and data-driven applications, users are demanding real-time access to everything everywhere. System resilience and responsiveness are no longer "nice to have", they are essential business requirements. Businesses increasingly need to trade up from static, fragile architectures in favor of flexible, elastic systems. These changes are happening because the application requirement has changed dramatically in recent years.
Only a few years ago a large application had tens of servers, seconds of response time, hours of offline maintenance and gigabytes of data. Today application is deployed on everything from mobile devices, IoT devices, Artificial Intelligence platform, Big Data to cloud base clusters running thousands of multi-core processors. The user expects millisecond response times and 100% uptime. Data is measured in Petabytes. Today's demand is simply not met by yesterday's software architectures. We believe that a coherent approach to systems architecture is needed, and we believe that all necessary aspects are already recognized individually. We want systems that are Responsive, Resilient, Elastic and Message Driven. We call this Reactive Systems.
?Reactive Systems are more flexible, loosely-coupled and scalable. This makes it easier to develop and amenable to change. They are significantly more tolerant of failure and when failure does occur they are with the elegance rather than a disaster. Reactive System is highly responsive and giving users effective interactive feedback.
Characteristics of A Reactive Systems
Responsive: The system responds in a timely manner if all possible. Responsiveness is the cornerstone of usability and utility, but more than that, responsiveness means that problems may be detected quickly and dealt with effectively. Responsive systems focus on providing rapid consistent response times, establishing reliable upper bounds so they deliver a consistent quality of service. This consistent behavior, in turn, simplifies error handling builds end-user confidence and encourages further interaction.
Resilient: The systems stay responsive in the face of failure. This applies not only to a highly-available, mission-critical system any system that is not resilient will be unresponsive after a failure. Resilience is achieved by replication, containment, isolation, and delegation. Failure is contained within each component, isolation components from each other and thereby ensuring that parts of the system can fail and recover without compromising the system as a whole. Recovery of each is delegated to another external component and high availability is ensured by replication where necessary. The client of a component is not burdened with handling its failures.
Elastic: The system stays responsive under varying workloads. Reactive System can react to changes in the input rate by increasing or decreasing the resources allocated to services these inputs. This implies designs that have no connection points or central bottlenecks, resulting in the ability to shard or replicate components and distribute inputs among them. Reactive System predictive, as well as Reactive, scaling algorithms by providing relevant live performance measures. They achieve elasticity in a cost-effective way on commodity hardware and software platforms.
Message Driven: Reactive system relies on asynchronous message-passing to establish a boundary between components that ensures loose coupling, isolation and location transparency. This boundary also provides the means to delegate failures as a message. Employing explicit message-passing enable load management, elasticity and flow control by shaping and monitoring the message queues in the system and applying back-pressure when necessary. Location transparent message are a means of communication makes it possible for the management of failure to work with the same contracts and semantics across a cluster or within a single host. Non-blocking communication allows recipients to only consume resources while active leading to less system overhead.
Before we dive into Akka Model discussion let us look at the 3 Parallelism models we have
- Actor Model Erlang, Scala, Rust, Java, .Net
- Communication Sequential Processes (CSP) Golang
- Threads Java, C#, C++, C, and others.
What is Actor Model: "The actor model in computer science is a mathematical model of concurrent computation that treats "actors" as the universal primitives of concurrent computation. Actors may modify their own private state, but can only affect each other indirectly through messaging (obviating lock-based synchronization) "... Wikipedia
Akka Actor Better Solution for Multithreaded Systems
The Akka Actor model elegantly solves this dilemma, providing a foundation for truly multithreaded applications. The actor model is designed to be message-driven and non-blocking, with throughput as part of the natural equation. It gives developers an easy way to program against multiple cores without typical in concurrency issues we face in multithreaded environments.
What is an Akka Actor Model: Akka has the concept of Actors, which provide an ideal model for thinking about highly concurrent and scalable systems. The actor model is a design pattern for writing concurrent and scalable code that runs on distributed systems.
What is Akka: Akka is a toolkit and runtime for building highly concurrent, distributed, and resilient message-driven applications on the JVM. It can be written in Scala or Java programing language.
Why Actor Model: Multi-threaded programming runs multiple copies of your application code in their own threads and then synchronizing access to any shared objects. Multi-Threading application is very hard to build while it’s a complex issue, multi-threaded programming has three major faults.
- Shared objects - protected by synchronized block or method but blocking
- Deadlock - first thread tries to access a synchronized block of the second thread, while second thread tries to access synchronized block from first thread resulting in a deadlock
- Scalability - managing threads on multiple JVMs.
While the Actor model is not new and long exist in Haskell and Erlang. Akka run time make it straightforward to writes actor-based application
Anatomy of Actor Model
- Actor System: is a glue that wires Actors, ActorRefs, Dispatchers, and Mailboxes together. Developers use the ActorSystem to configure the desired implementation of each of these components. It creates and initializes actors.
- Next, the actor creation process is initiated by the sender (usually itself) which invokes actor() on ActorSystem creates a reference of Actor object. At this time Actor may not be created/initiated. ActorRef acts as a proxy to the Actor object and provides an interface to communicate with Actor.
- Sender Actor (Caller – in this case, “actor itself from step 2”) uses ! (bang operator is known as tell message pattern – “fire-and-forget”, e.g. send a message asynchronously and return immediately.) to tell the receiving Actor about the event (Hi Actor, Can you please process this event?)
- ActorRef in response dispatches the event on MessageDispatcher (you can configure ActorSystem to use specific dispatcher by invoking with a dispatcher)
- MessageDispatcher enqueues the event to Message Queue.
- MessageDispatcher also looks for MailBox (By default every actor has a single mailbox – Unbounded Mailbox). Mailbox holds the messages for receiving Actor. Once it finds the MailBox – MessageDispatcher binds a set of Actors to a thread pool (backed by BlockingQueue) and invokes MailBox.run() method
- Message Queue.dequeue() – dequeue the next message from this queue, return null failing that.
- Eventually, MailBox schedules the task on Actor which invokes receive() method on the Actor.
The following are Akka modules in Akka tool kits.
Akka HTTP: The Akka HTTP modules implement a full server- and client-side HTTP stack on top of Akka-actor and Akka-stream. It’s not a web-framework but rather a more general toolkit for providing and consuming HTTP-based services. Akka HTTP Modern, fast, asynchronous, streaming-first HTTP server and client.
Akka Stream: Akka Streams is an implementation of the Reactive Streams specification on top of the Akka toolkit that uses the actor-based concurrency model. Reactive Streams specification has been created by the number of companies interested in asynchronous, non-blocking, event-based data processing that can span across system boundaries and technology stacks.
Akka Cluster: provide a fault-tolerant decentralized peer-to-peer based cluster membership provides a fault-tolerant decentralized peer-to-peer based cluster membership service with no single point of failure or single point of bottleneck. It does this using gossip protocols and an automatic failure detector. Akka cluster allows for building distributed applications, where on application or service spans multiple nodes (in practice multiple ActorSysems)
Akka Cluster Sharding: Cluster sharding is useful when you need to distribute actors across several nodes in the cluster and want to be able to interact with them using their logical identifier, but without having to care about their physical location in the cluster, which might also change over time. Cluster sharding is typically used when you have many stateful actors that together consume more resources (e.g. memory) than fit on one machine. If you only have a few stateful actors it might be easier to run them on a Cluster Singleton node.
Akka Distributed Data: Akka Distributed Data is useful when you need to share data between nodes in an Akka Cluster. The data is accessed with an actor providing a key-value store like API. The keys are unique identifiers with type information of the data values. The values are Conflict-Free Replicated Data Types (CRDTs).
Akka Persistence: Akka persistence enables stateful actors to persist in their state so that it can be recovered when an actor is either restarted, such as after a JVM crash, by a supervisor or a manual stop-start, or migrated within a cluster. The key concept behind Akka's persistence is that only the events received by the actor are persisted, not the actual state of the actor (though actor state snapshot support is also available). The events are persisted by appending to storage (nothing is ever mutated) which allows for very high transaction rates and efficient replication. A stateful actor is recovered by replaying the stored events to the actor, allowing it to rebuild its state. This can be either the full history of changes or starting from a checkpoint in a snapshot which can dramatically reduce recovery times. Akka persistence also provides point-to-point communication with at-least-once message delivery semantics.
Defining an Actor
The key abstraction in Akka is the Actor, which provides behavior and can store state. In Akka, actors are guaranteed to be run in a single-threaded illusion which means that the Akka framework takes care of threading and concurrency while allowing us to focus on the behavior that needs to be implemented.
Actors may only communicate with others and the outside world through messages. Any immutable object use as a message in Akka, so a simple string could be used as a message, but case object and case classes in Scala are often used for messages. Actors are message-driven i.e they are passive, they are only reactive when the message is sent to them. One message is sent to an Actor, they pick a thread from the thread pool which also known as dispatch, process the message and release the thread back to the pool. The actors never block your current thread of execution, they are asynchronous by nature.
An actor represents an independent computation unit.
The following are some important characteristics of an Actor.
- An Actor encapsulates its state and part of the application logic
- Actors interact only through asynchronous messages and never through direct method calls (non-blocking)
- Each actor has a unique address and a mailbox in which other actors can deliver messages (location transparency)
- The actor will process all the message in the mailbox in sequential order ( the default implementation of the mailbox being a FIFO queue you can override and implements your own depending on use case)
- The actor system is organized in a tree-like hierarchy
- An Actor can create other actors, can send messages to any other actor itself or any actor it has created.
Properties of An Actor
State: Actor object will typically contain some variable which reflects possible states the actor may be. This can be an explicit state machine or it could be any representation of object states e.g counter, request, account balance, etc. These are data what make actor valuable, and they must be protected from corruption by other actors. Akka actors conceptually each have their own light-weight thread, which is completely shielded from the rest of the system. This means that instead of having to synchronize access using locks you can just write your actor code without worrying about concurrency at all. Behind the scenes, Akka will run a set of actors on the real threads where typically many actors share one thread and subsequent invocations of one actor may end up being processed on different threads.
Behavior: Every time a message is processed, it is matched against the current behavior of the actor. Behavior means to function or method which defines the action to be taken in reaction to a message at that point in time, say forward a request if the client is authorized, deny it otherwise, The behavior may change another time.
MailBox: An actor's purpose is the processing of messages and these messages are sent to the actor from another actor or from outside the actor system. The piece which connects the sender and receiver is the actor's mailbox. each actor has exactly one mailbox to which all send enqueue their messages. Enqueuing happens in the time-order of send operations. which means the message is sent from the different actors not have a defined order at runtime due to the apparent randomness of distributed actors across threads.
Example of An Actor in Scala and Java
/** Scala Implementation **/ class WeatherActor extends Actor { // state inside the actor var temperature = 0 // behaviour which is applied on the state override def receive: Receive = { // receives message an integer case IncrementTempCommand(temp): => updateTemperation(temp) println(s"The state of weather temperature is $temperature ") // receives default message case _ => println("I don’t know whatare you talking about") } } def updateTemperation(temp int) { temperature = temperature + temp } /** Java Implementation **/ public class WeatherActor extends AbstractActor { { public static class IncrementTempCommand { private final int temp ; public IncrementTempCommand (int tem) { this.temp = temp; } public String getNextTemperature() { return this.temp; } int temperature = 0; @Override public Receive createReceive() { return receiveBuilder() .match(IncrementTempCommand.class, c -> { updateTemperature(c.getNextTemperature()) } } }).build(); private void static updateTemperature(int temp) { temperature = temperature + temp; } }
Here we are not creating an actor, we are only defining the state and behavior.
Akka prevents us from getting direct access to an Actor and thus ensures that asynchronous messaging is the only way to interact with it: It’s impossible to invoke a method on an actor. It’s also worth pointing out that sending a message to an actor and processing of that message by the actor are two separate activities, which most probably happen on different threads — of course, Akka takes care of the necessary synchronization to make sure that any state changes are visible to any thread.
Actor Hierarchy
An actor in Akka always belongs to a parent. Typically you create an actor by calling context.actorOf(). Rather than creating a free-standing actor, this injects the new actor as a child into an already existing tree. The creator actor becomes the parent of the newly created child actor. As illustrated below all actors have a common parent, the user guardian or supervisor actor. A new actor instance can be created under this actor using system.actorOf(). e.g if we create an actor named SomeActor with system.actorOf(....."someActor") . the reference path will be /user/someActor.
ActorSystem
In Akka, An ActorSystem is the starting point of any Akka application that we write.
Technically, an ActorSystem is a heavyweight structure per application, which allocates n number of threads. Thus, it is recommended to create one ActorSystem per application, until we have a reason to create another one. An actor system is hierarchical group actors which share common configuration e.g dispatchers, deployments, remote capabilities, and addresses. It is also the entry point for creating or looking up actors
Actor Communication
Actors have methods to communicate with each other actors tell (!) or ask (?) where the first one is fire and forget and the second return a Future which means the response will come from that actor in the future.
Ask will send the message and return a future, which can be awaited until timeout or reply is received, tell will send the message and return immediately.
Akka Actor Use Cases
- Transaction Processing (Online Gaming Banking, Insurance, Fintech, Trading Betting, Social media, Telecom, HealthCare)
- Backend Service (Any industry Scale-up, Scale-out, Fault-tolerance /HA, Distributed, Sharding, Cluster)
- Batch Processing (Any industry)
- IoT Telemetry Data Processing (Scale-up, Scale-out, Fault-tolerance / HA)
- Big Data Streaming Processing
- Complex Event Processing
- Machine Learning Model
I am going to demonstrate how to build some of these use cases in my upcoming articles.
Thank you for reading.
Oluwaseyi Otun is Software and Data Engineer, Backend Software Engineer (Java, Golang, Scala). Big Data enthusiast with a special interest software architectural design pattern, microservice, Big Data, large scale distributed system in in-memory, streaming computing and big data analytics. I love learning internals working on systems and exploring what happens under the covers. He lives in one of the Atlantic Province in Canada.