Microsoft Azure Cloud Services is a PaaS offering that simplifies the task of deploying scalable applications. An Azure PaaS deployment comprises two files: a package containing the application assemblies; and a configuration file. This simplicity makes Azure Cloud Services a great environment for deploying scalable applications. However, the developer remains responsible for ensuring that the application functions well in the distributed environment provided by Azure Cloud Services.
Microsoft Research developed Project Orleans with the specific goal of simplifying the creation of scalable applications for “developers who are not distributed system experts.” Orleans is an implementation of an Actor model, using the constraints imposed by that model to reduce the complexity of developing a distributed system. This simplifies the development of certain classes of distributed system.
Orleans combines an application framework with a service runtime. The application framework abstracts away certain elements that complicate the development of distributed systems. The service runtime provides a simple model that supports application deployment into various environments from a single PC up to an Azure Cloud Service. An Orleans application is a composite of a client application (e.g., a website) and an Orleans server application hosted by the runtime. Orleans is in preview but is currently used to provide some high-scale, backend services, hosted in Azure, for games such as Halo 4.
The Orleans preview can be downloaded from Microsoft Connect. This download comprises the application framework, including Visual Studio tooling, and the Orleans runtime. The Orleans documentation along with various samples are hosted on CodePlex.
Microsoft Research hosts the home page for Orleans. The Orleans team has written a very readable research report that describes the Orleans architecture in some depth (be sure to read the 2014 version). Hoop Somuah (@hoopsomuah) and Sergey Bykov (@sbykov_work) did a Build 2014 presentation on Using Orleans to Build Halo 4’s Distributed Cloud Service. Richard Asbury (@richorama) talks about Orleans in a .Net Rocks Podcast. Caitie McCaffrey (@CaitieM20) has a post on Creating RESTful services using Orleans.
The rest of this post is a high-level look at some Orleans features. I intend to do a follow-up going deeper into them.
The Actor model was introduced in 1973 by Hewitt, Bishop and Steiger. In this model, an actor is the fundamental primitive for concurrent compute providing processing, state and communication. A system comprises many actors, which interact by sending messages to each other. An actor encapsulates state and data is not shared between actors. An actor processes a message in the following ways:
- Create new actors
- Send messages to other actors
- Designate how to handle the next message it receives
The intent is that an actor is a simple entity with message processing being a manifestly concurrent operation that may change the internal state of the actor (and thereby the way subsequent messages are handled). A complex system is built through the interaction of many actors. By ensuring the concurrency of basic message processing, the actor model simplifies the creation of sophisticated distributed systems where concurrency can often cause significant problems.
Orleans is an implementation of the Actor model. In Orleans, an actor is referred to as a grain and the runtime host for grains is referred to as a silo. A grain is an instance of a .NET class implementing a marker grain interface. In a distributed Orleans system, there is a silo on each server hosting the Orleans runtime. An individual grain can be hosted in any of the Orleans silos, but the runtime provides location transparency so the user does not know which silo holds a particular grain.
The Orleans runtime implements a model in which grains are deemed to have an eternal, but virtual, life. A grain is deemed active when it is physically resident in a silo and is otherwise deemed inactive. When a request for a grain is made, the runtime either returns a reference to a grain already activated in some silo or silently activates a grain in a silo and returns a reference to it. The caller is completely isolated from grain activation. This allows the runtime to manage resources efficiently by silently deactivating grains that have not been used for a while.
By default, the Orleans runtime does not provide affinity between a grain and a silo. This flexibility allows the runtime to hydrate a grain into any silo, and this allocation may change through the virtual lifetime of the grain. The runtime uses an internal discovery service to identify the silo containing a grain. The discovery service uses a distributed hash table located on each silo to store the identity of the silo currently containing a grain. As an optimization, each silo has a local cache which stores the location of recently-accessed grains.
Since a grain can be in any silo it is always accessed through a reference provided by the Orleans runtime. When a message is sent to a grain the runtime:
- makes a deep copy of it
- serializes it using a specialized binary serializer
- transmits it to the correct silo
- deserializes it
- queues it for processing by the receiving grain.
Orleans completes the message-sending process by invoking a method on the receiving grain. The message invocation results in a promise that may or may not complete successfully, that is the promise may be fulfilled or it may be broken. The promise is implemented using the .NET Task classes, and is greatly simplified through use of the .NET 4.5 async/await feature. Message passing is an asynchronous operation so the caller is not immediately aware of the success or failure of the method invocation. This asynchrony is crucial to the scalability of Orleans, since the runtime is able to schedule invocation without blocking the caller.
A grain reference can be used either inside another grain hosted in an Orleans server application or in a client application hosted outside the Orleans runtime.
A grain is defined through the specification of an interface and the creation of a class implementing it. Orleans build-time tooling automatically generates a factory class for each grain class allowing references to grains of it to be retrieved.
The grain interface is derived from IGrain. The interface comprises one or more public methods returning either a Task or a Task<T>. A message to a grain corresponds to the invocation of one of the grain interface methods.The Orleans runtime handles the transfer of the method invocation from the sending grain through the messaging infrastructure to the receiving grain which, in a distributed system, is likely to be in a silo on another server.
The Orleans grain implementation is defined by creating a class derived from GrainBase that implements the interface. There is no need to define a constructor for the class, since auto-generated factory methods are used to create references to grains. One or both of ActivationAsync() and DeactivationAsync() methods can be defined to contain any specific grain activation and deactivation code.
The Orleans framework tooling creates the factory class for each grain type, and this process generates an error when it detects an invalid grain interface. The factory class exposes a set of factory methods used to retrieve a grain reference. Note that retrieving a reference does not lead to grain activation, which is done only when a message is sent to the grain.
Each grain is identified by its type and primary key, which is either a GUID or an Int64. Internally, the latter is zero-padded into the former. (It is also possible to declare a grain type with an extended primary key that includes a String.) By default, each specific grain is a singleton but it is possible to declare a stateless worker grain that the Orleans runtime can scale out automatically.
The Orleans runtime schedules work as a sequence of turns, with a turn being the execution of a grain method up to the time a promise has been received (e.g., reaching an await statement; the closure following an await statement; or the return of a completed or uncompleted Task). To avoid concurrency problems, each grain is single-threaded so that only one turn is executed on a grain at any one time. A single request may result in several turns and, by default, the runtime processes all the turns for a request before processing any other requests for the same grain. Orleans provides high-scale by hosting many grains on a single server, so that even though request handling on each grain is single threaded the handling of individual requests on many grains is performant.
Orleans uses a purpose-built scheduler that provides cooperative multitasking instead of the preemptive multitasking of the standard .NET Task scheduler. For the turn-based scheduling used by Orleans this provides for much more efficient use of system resources than preemptive multitasking for the Orleans runtime.
The Orleans runtime can load persistent grain state on activation. This is independent of the ActivationAsync() method. For performance reasons grain state is not persisted automatically, instead state persistence for the grain must be explicitly managed by the grain implementation.
The Orleans support for grain state persistence is implemented by creating a class derived, from IGrainState, to hold that state. The grain class implementation must be derived from GrainBase<T> (instead of GrainBase), and implement the grain interface, where T is the class holding the grain state. The grain class can have additional state, stored in non-persisted private members, that can be initialized using ActivationAsync(). The IGrainState interface exposes WriteStateAsync() and ReadStateAsync() methods that are used to persist grain state and refresh grain state from the persistent store.
Orleans has the concept of pluggable storage providers to support grain state persistence. The storage provider is specified in the Orleans server and client configuration files. Several storage providers ship in the preview: LocalMemory is a development provider using local memory; AzureStorage persists grain state in Azure Tables (either cloud or development storage). Orleans provides a relatively simple extension point allowing the creation of additional storage providers. One of the samples demonstrates how to do this. Richard Astbury has published a storage provider using Azure Blob Storage.
Visual Studio Tooling
The Orleans framework provides three Visual Studio project templates for Orleans:
- Orleans Dev/Test Host – creates a console app with an Orleans silo for development purposes
- Orleans Grain Class Collection – contains the grain class implementations
- Orleans Grain Interface Collection – contains the grain interfaces
The Orleans build tooling creates the grain factory classes used to access grain references. The files for these classes are located in the Properties\orleans.codegen.cs file under the interfaces directory for the project.
Orleans can be deployed locally for dev/test purposes. It can also be deployed into group of local servers. However, a scalable system should be deployed into Azure.
A common Azure deployment is to host the Orleans server application in an Azure worker role and the client application in an Azure web role. The Orleans framework makes it trivial to deploy an Orleans server into an Azure worker role. Indeed, there is a one-to-one match between an Orleans runtime method and the Azure RoleEntryPoint overrides. The Orleans runtime is able to handle the scaling out of the worker role instances. In an Azure deployment, the Orleans runtime uses Azure Tables to store runtime information.
The development and deployment of scalable distributed systems is difficult. Project Orleans provides an application framework and runtime support that simplifies the creation of those distributed systems that can be implemented using an Actor model. Orleans is specifically designed to simplify the creation of distributed systems by developers who are not experts in distributed systems. It is also designed to play well with Azure, and clearly demonstrates the benefit of developing cloud-native applications for Azure Cloud Services. Orleans comes with Visual Studio tooling, documentation, and samples which make it easy to learn how to use it.