ted.neward@newardassociates.com | Blog: http://blogs.newardassociates.com | Github: tedneward | LinkedIn: tedneward
Our goal here is to:
Explore what a "virtual actor" is and how it works
See how Orleans implements these virtual actors
Talk about Orleans-centric distributed system design
What is it?
actors model for CLR technology stacks
"grains"
stateless or persistent
virtual actors: "always alive" semantics
open-source
https://github.com/microsoft/orleans
used on some sizable projects
Halo 4, Halo 5
Grain
Silo
Cluster
Host
"Virtual Actor": Identity + Logic + State
defined observable/"hook"able lifecycle
Single-threaded, serial message receipt
single living instance: "activation"
Objects have implicit identity (memory address)
Actors use more generalized identity
Orleans grains must have "long-lived" identity
Orleans uses one of five options for identity:
long
GUID
string
GUID + string (compound)
long + string (compound)
Grains can be entirely stateless
singletons, in essence
can allow multiple activations
Grains can hold state
state is held across lifetime of server
state can be persisted to external storage
Describes the messaging surface
uses CLR interfaces and async idioms
only type visible to clients
"IDL" for Orleans
bundle of data passed across the wire
binary format; serialized (deep copy)
actual runtime type passed (not declared type)
Orleans generates "serializers" as part of build
can be invoked await (preserves order) or async (no ordering)
server instance/process, containing grains
manage allocation/lifecycle of grains
provides location transparency
forces "remote-first" mentality
designed to work together in a cluster
defined observable/"hook"able lifecycle
collection of silos
responsible for grain placement in Silos
random
local-to-caller (server)
hash-based
activation-count-based
configurable and customizable
process which contains the running server(s)
most often a Console, Service, or ASP.NET app
heavy use of Microsoft Hosting classes
Grains always exist
runtime activates them on demand
runtime deactivates them "later" or on request
Everything is a Task or Task-of-T
enforcing/requiring asynchronicity
a form of inter-process communication (IPC)
extend concept of procedure calls across the network
library/infrastructure handles the transmission details
so it all looks "just like local" to developers
two processes want to communicate
specifically we want:
clients to initiate the request
clients block while server is processing
well-defined inputs/outputs
as much infrastructure to "disappear" as possible
enter remote procedure calls
server writes a function/method/endpoint
exposes/advertises it
either for ad-hoc discovery
or to a well-known endpoint (host/port/etc)
client calls a local function
magic happens
server executes call, returns results
more magic
client gets return value, carries on
client calls a local function
client marshals call information into a wire format
function name (and param types, if overloadable)
parameters
(ORPC only) remote object instance identifier
client connects to server, sends wire formatted request
server receives wire formatted request
unpacks call info, dispatches it locally
server executes call, returns results
server marshals results (return value or exception)
sends wire formatted response back to client
client receives wire formatted response
client unmarshals response and returns from local call
either returning return value
or throwing exception
based on whichever happened on server
transport: over what channel do we communicate?
TCP
UDP
HTTP
protocol: what does the wire format look like?
binary/proprietary
XML
JSON
dispatcher: how does infrastructure map to network?
identifying endpoints/calls
(sometimes) threading support
(sometimes) discoverability
RPC is sometimes characterized as "messaging"
message-out matched by message-in
RPC can be/is often built on top of messaging technologies
any messaging system can be used for RPC
so long as request can be tied to response
and client blocks until response is received
... and RPC can be used to build messaging systems
target queue: server
senders, receivers: clients
this can include other queues!
clients block until the server responds
the network is "hidden" from the caller
what happens if something goes wrong?
easy to make fatal mistakes and be too "chatty"
more difficult to version
less flexibility in some languages
requires some kind of "contract" between clients/servers
types (code) must be shared across both sides
how do we handle callbacks?
suggested in 1973 by Carl Hewitt
later, 1974: Sir Tony Hoare, Communicating Sequential Processes (CSP)
inspired by physics
as well as by Lisp, Simula, and early Smalltalk
implemented natively in Erlang
"... model of concurrent computation that treats the actor as the universal primitive of concurrent computation." (Wikipedia)
lightweight entities (actors)
on the surface, similar in concept to objects
below the surface, some very different nuances
also known in some circles as Communicating Sequential Processes (CSP)
an actor is an atomic unit of computation
actors can have local (non-shared!) state
actors communicate (only) by sending messages
upon receipt of a message, an actor can:
create more actors
send messages to other actors
designate what to do on the next message (eg, mutate internal state)
an actor will only ever be processing a single message at a time
each actor has a "mailbox" by which to receive messages
uniquely identifiable for each actor (eg, "actor:mailbox")
can either be implicit or explicit
can be either local-only or remote-accessible
share no state directly with other actors
often operate as the sole design mechanic
that is, "everything is an actor"
we don't mix actors and objects at the same conceptual level
asynchronously delivered; no blocking
any message can be sent to any actor
unknown/unrecognized messages are ignored
may be delivered out of any particular order
may or may not generate a response
Pro:
zero blocking; greatly reduced concerns around thread safety/concurrency
state management is often more encapsulated/convient, therefore easier to reason
Con:
different programming (and mental) model
more complex to implement than a "traditional" object
Microsoft .NET (.NET 10.0)
... and a network
https://github.com/tedneward/Demo-Orleans/HelloWorld
four projects:
interfaces (IDL)
implementations
client
server/host
any CLR language (most often C#)
ClassLibrary
requires
Microsoft.Orleans.Core.Abstractions
Microsoft.Orleans.CodeGenerator.MSBuild
IHelloWorld
public interface IHelloWorld : Orleans.IGrainWithIntegerKey
{
Task<string> SayHello(string name);
}
ClassLibrary
requires
Microsoft.Orleans.Core.Abstractions
Microsoft.Orleans.CodeGenerator.MSBuild
Interfaces
HelloWorld
public class HelloWorld : Orleans.Grain, IHelloWorld
{
private readonly ILogger _logger;
public HelloWorld(ILogger<HelloWorld> logger) { _logger = logger; }
public Task<string> SayHello(string name)
{
_logger.LogInformation("SayHello: name = '{name}', ", name);
return Task.FromResult($"Hello, {name}, welcome to Orleans");
}
}
Console
requires
Microsoft.Orleans.Server
(Microsoft.Extensions.Hosting)
(Microsoft.Extensions.Logging.Console)
Interfaces
Grains
Program.cs
var builder = new HostBuilder()
.UseOrleans(c =>
{
c.UseLocalhostClustering()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "dev";
options.ServiceId = "HelloWorld";
})
.ConfigureApplicationParts(
parts => parts.AddApplicationPart(typeof(HelloWorld).Assembly).WithReferences())
.ConfigureLogging(logging => logging.AddConsole());
});
var host = builder.Build();
await host.StartAsync();
Console
requires
Microsoft.Orleans.Client
Interfaces
Client Setup
var client = new ClientBuilder()
.UseLocalhostClustering()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "dev";
options.ServiceId = "HelloWorld";
})
.ConfigureLogging(logging => logging.AddConsole())
.Build();
await client.Connect();
Console.WriteLine("Client successfully connected to silo host \n");
Client calling host
var friend = client.GetGrain<IHelloWorld>(0);
var response = await friend.SayHello("Good morning, HelloGrain!");
must implement grain interface (C# interface)
must extend Orleans.Grain base class
optionally override OnActivateAsync/OnDeactivateAsync
for notification of activation/deactivation
exceptions will cancel activation
deactivation is not guaranteed (e.g., server crash)
primary key available via GetPrimaryKey
grains are never accessed directly
clients must obtain a "grain reference"
pass identity to GrainFactory.GetGrain
references can be used as typical .NET objects
define the (strongly-tyoed) interaction between sender and recipient
conceptually similar to older approaches (CORBA, DCOM, etc)
... but tweaked to reflect Orleans' particular opinions
used to ensure implementation and client-proxy correctness
basis for code generation during build
must extend an "identity interface"
describes the kind of primary key
IGrainWithGuidKey
IGrainWithIntegerKey
IGrainWithStringKey
IGrainWithGuidCompoundKey
IGrainWithIntegerCompoundKey
methods must return Task
Persistent grains
Timers and reminders
Streams
Heterogenous Silos
Evolving/versioning Grains
Event sourcing
Transactions
virtual actor system
built on top of CLR
providing easy language-to-messaging syntax/semantics
capable of supporting high scale/load
open-source
Orleans OSS repository
https://github.com/dotnet/orleans
Official docs
https://learn.microsoft.com/en-us/dotnet/orleans/
OrleansContrib
https://github.com/orgs/OrleansContrib
Awesome-Orleans
https://github.com/OrleansContrib/Awesome-Orleans
"Introducing Microsoft Orleans"
https://www.amazon.com/Introducing-Microsoft-Orleans-Implementing-Cloud-Native/dp/148428013X
"Microsoft Orleans for Developers"
https://www.amazon.com/Microsoft-Orleans-Developers-Cloud-Native-Distributed/dp/1484281667
"Distributed .NET w/Microsoft Orleans"
https://www.amazon.com/Distributed-NET-Microsoft-Orleans-applications-ebook/dp/B09NPBDQSL
Architect, Engineering Manager/Leader, "force multiplier"
http://www.newardassociates.com
http://blogs.newardassociates.com
Sr Distinguished Engineer, Capital One
Educative (http://educative.io) Author
Performance Management for Engineering Managers
Books
Developer Relations Activity Patterns (w/Woodruff, et al; APress, forthcoming)
Professional F# 2.0 (w/Erickson, et al; Wrox, 2010)
Effective Enterprise Java (Addison-Wesley, 2004)
SSCLI Essentials (w/Stutz, et al; OReilly, 2003)
Server-Based Java Programming (Manning, 2000)