Recently, I have been fortunate enough to undertake some Azure training under one of the Solution Architects at Microsoft. We spent a lot of time focusing on Azure Service Fabric, Microsoft’s platform for developing microservice-based solutions. Much of what I heard reminded me of my experiences of Docker Swarm and Kubernetes, two very popular container orchestration platforms, and many of my questions in the training sessions were centered around why I might want to use Service Fabric over one of these other platforms that I knew a little better.
One point the trainer made that has stuck with me since is: containers often contain microservices, but microservices are not containers. It was clear to me then that I had wrongly conflated the two in my thinking. One of the things that made Service Fabric compelling was that not only did it support containers and could orchestrate them, but also provided alternative frameworks and programming models for developing microservices that were not container-based.
The training session alone was not deep enough for me to fully evaluate these, but sometimes the best thing to learn is that you don’t know enough about something. I knew I needed to go back to first principles with Service Fabric and discover what it had to offer for myself.
As I’ve already mentioned, Service Fabric has a selection of programming models to choose from. Reliables Services can be Stateless or Stateful and are fairly self-explanatory, one step beyond a Stateful Service (in that it builds on top of them) is the Reliable Actors framework.
When Reliable Actors got mentioned to the training cohort, the term felt familiar enough that my brain went hunting in its darkest corners for a definition, I recognized it but I couldn’t quite define it. It felt familiar because the Actor model of computation is actually quite an old idea, I must have come across it at university and never really applied it well enough to retain it properly. The model originates from the 1970s as an approach to implementing concurrent systems and has been further improved upon over the years. In 2010, Microsoft Research released a paper with several enhancements that it called the Virtual Actor model, it has various semantics that grant advantages over old-school concurrent programming with threads, locks and method synchronization. This became the basis of the Reliable Actors framework.
To be, or not to be?
So what characteristics define the Virtual Actor model? Let’s take a look:
No Global State
Actors are well-encapsulated and share no global state with other actors. They can only communicate with each other by passing asynchronous messages between them. This pretty much eliminates the scope for shared resource contention and deadlocks amongst actors. It’s typical for an actor only to have a small amount of local state and as this is never directly shared with any other actor there is no need for locks on shared state.
This rule includes databases, as used in the traditional three-tier architectural style. It is difficult to achieve hyperscale when you have a stateless middle-tier on top of a central datastore, the finite throughput you can achieve between those layers becomes the bottleneck. Reliable Actors can ascend to greater levels of scalability by keeping all state local to the entities that require it, increasing performance and reducing resource contention.
Multiple actors can run on a host concurrently, but each actor is single threaded and cannot service multiple requests at once.
In Service Fabric, this manifests itself in the turn-based concurrency model of the Reliable Actors runtime. A turn is when an actor is allowed to execute a method in its entirety, without interruption, in response to a single request. Because each method is effectively atomic, the model greatly reduces the need for synchronization mechanisms within the actor.
Like a database record, an actor can live long after its in-memory representation. If an actor is not used for a period of time, it is garbage collected and any resources consumed on the Service Fabric cluster are freed. If a message is sent to an actor afterward, it causes a new object with the same state to be constructed.
Highly Available and Scalable
Actors run inside of a stateful Reliable Service, meaning that they get automatically distributed across different partitions on your Service Fabric cluster for redundancy and efficient use of compute resources. Service Fabric can also migrate actors from failed nodes to healthy ones as needed, with no noticeable downtime.
To use an actor object, client code creates an ActorProxy that implements the same interface as the actor service, method calls then get passed over to the real actor by the runtime. This means that client code does not need to worry about whether the actor object needs reinitializing, or what node in the cluster it is currently hosted on, etc.
Reliable Actors are a great choice when you have lots of entities with well-encapsulated state. For example, they’re great for representing something like an individual post on a social network, a standalone entity that doesn’t have to relate to other entities of its type. The local state might be the distribution graph of the post, likes, shares, etc. A post never really dies (you can scroll back on a newsfeed for as much time as you have the patience for) but they do definitely start hot and go cold with age, which matches quite well with how the runtime manages the in-memory representation of actors.
The consistency guarantees of the model and its origin as a solution to difficult concurrency problems make it suitable where high contention is likely. If you’re Ticketmaster and the Guns N’ Roses reunion tour gets announced, you’re going to want to use Reliable Actors in your ticket purchasing process!