The Actor Model
I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea.
The big idea is "messaging"
[..]
The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
[..]
-- Alan Kay
http://wiki.c2.com/?AlanKayOnMessaging
https://computinged.wordpress.com/2010/09/11/moti-asks-objects-never-well-hardly-ever/
Operating Systems
- Processes are the fundamental unit of computation
- Processes have independent state
- One process crashing should not impact another process
- Processes can be scheduled independently
- Processes communicate over pipes, queues, shared memory
Microservice Architecture
- Services are the fundamental unit of computation
- Services have independent state
- One service crashing should not impact another
- Services can be scheduled (and scaled) independently
- Services talk over a network, sometimes through TCP or Queues, no shared state or memory
Object Oriented Programming
- Objects are the fundamental unit of computation
- Objects can share state with other objects, expose their state (im)mutably to other objects
- One objects failure can corrupt or impact another object
- Objects do not lend themselves to independent scheduling of execution
- Objects communicate by sharing memory
foo = Foo()
bar = Bar()
try:
bar.do_thing(foo.get_inner_state())
except:
# What state is 'foo' in?
Actor Oriented Programming
- Actors are the fundamental unit of computation
- Actors can not share state with each other
- Actors isolate failure
- Actors can be scheduled individually
- Actors communicate via queues
An Actor Can
- send (0 or more) messages to themself or other actors
- create new actors
- change its own internal state
actor Foo
var some_state: U64 = 0
be update(value: U64) =>
some_state = some_state + value
let new_actor = Foo.create()
new_actor.update(some_state)
hey sup
nm u
nm
Time
Concurrency
- Within an Actor you write code sequentially
- Actor communication is asynchronous
- No locks, no shared memory, concurrency is just how you build your program
- Impossible to have data races because actors can not share memory
hey sup
nm u
nm
Time
hey :)
hi :)
Reliability
- Actors isolate failures
- Actors can easily model redundancy - create a pool of actors, route data to them (just like an ELB)
- Actors mimic real systems - sending a message to a computer that crashed shouldn't crash the sender, same idea with actors
- Used at Ericsson to achieve between 5 and 9 9's of availability with Erlang
hey sup
....
k nvm
Time
hey sup
Send Message
Await Response
Give up
Handle it
hey sup
hey sup
ERROR
hey sup
ERROR
...
Circuit Breakers
Time
Route message through CB
Time out
Return error
Return error immediately
Scalability
- Actors are cheap. +240 bytes of overhead in Pony. Very fast to spin up/ down, unlike OS threads.
- Need to scale? Add more actors. Need to scale more? Add more computers! You can treat computers as actors. Talking to an actor on your local system is similar (or identical) to talking to an actor on another system.
- Code written for 10 users is virtually identical to code written for 10_000_000 users
- Used by Whatsapp to scale to 1 billion users on the Erlang VM
Time
Do this 1 thing
Do this 1 thing
k done
Scaling To One User
Receive task
For every task, create new actor, assign it the task
Send completed tasks back
Do these 1000 thing
k done
Scaling To 1000 Users
Do this 1 thing
Time
Receive task
For every task, create new actor, assign it the task
Send completed tasks back
Pony
- Compiled, static type safety similar to Rust
- Capability safety (sandboxing)
- Exception safety (no unchecked exceptions), any function that can fail is modeled as a partial function
- Actors can not fail/ die
Erlang
- Dynamic type safety
- Focus on fail fast, supervisor tree structures
- Despite lack of optimizing power still capable of scaling well
- Actors can fail/ die
actor Player
let name: String
let env: Env
new create(name': String, env': Env) =>
name = name'
env = env'
be ping(opponent: Player) =>
env.out.print("Sending ping from " + this.name)
opponent.ping(this)
actor Main
new create(env: Env) =>
let player1 = Player.create("player1", env)
let player2 = Player.create("player2", env)
let player3 = Player.create("player3", env)
player1.ping(player2)
player1.ping(player3)
Sending ping from player1
Sending ping from player3
Sending ping from player2
Sending ping from player1
Sending ping from player1
Sending ping from player3
Sending ping from player2
Sending ping from player1
Sending ping from player3
Sending ping from player1
Sending ping from player1
Sending ping from player2
Sending ping from player1
Supervisor
- An actor whose only job is to watch other actors (or supervisors)
- Contains a policy for how to restart failed actors
Worker
- An actor who performs some arbitrary task
- No significant error handling exists in workers
- Problem? Let the supervisor figure it out.
Supervisor
Â
Â
Worker
r.i.p
i got this
Asset Authentication App
- Provide REST API to query for Auth information
- Turn Attributed Auth Docs into Behaviors
- Store Auth information into Cassandra
- Generate Log Content
Input Queue
Output Queue
C*
REST API
Persistors
Router
Behavior Generator
Log Converter
C*
Output
Queue
What do we want?
- Modular, extendable features with clear boundaries
- Primary features should always work, never impact each other
- Easily scale if one feature starts to get heavier load
Why Not Microservices?
- Increased latency
- Ownership of code would not change
- Still mostly single purpose, just with multiple 'features'
- Just doesn't seem worth it for something that's already quite small and already fits the microservice paradigm decently
C*
REST API
Input Queue
Output Queue
Persistors
Router
Behavior Generator
Log Converter
C*
- REST API
- Behavior Generation
- Persist Auth Info
- Log Content
Router
C*
Persistors
Persistors
Router
Behavior Generator
C*
C*
actor Router
var generators = BehaviorGeneratorPool
be route_message(msg: Datatype) =>
for thing_to_persist in msg.persist_stuff() do
let persistor = Persistor.create()
persistor.persist(thing_to_persist)
end
generators.gen(msg)
Persistors
Router
Behavior Generator
C*
actor Persistor
let connection: CassandraConnection
new create() =>
connection = CassandraConnection.create("some cassandra url idk")
be persist(data: SomeDatatype) =>
# validation, transformation, whatever
connection.store(data)
Persistors
Router
Behavior Generator
C*
Persistors
Router
Behavior Generator
C*
C*
Akka
public class SimpleActor extends UntypedActor {
LoggingAdapter log = Logging.getLogger(getContext().system(), this);
public SimpleActor() {
log.info("SimpleActor constructor");
}
@Override
public void onReceive(Object msg) throws Exception {
log.info("Received Command: " + msg);
if (msg instanceof Command) {
final String data = ((Command) msg).getData();
final Event event = new Event(data, UUID.randomUUID().toString());
// emmit an event somewhere...
} else if (msg.equals("echo")) {
log.info("ECHO!");
}
}
}
Companies Using Akka In Production
http://doc.akka.io/docs/akka/2.0.4/additional/companies-using-akka.html
We saw a perfect business reason for using Akka. It lets you concentrate on the business logic instead of the low level things. It’s easy to teach others and the business intent is clear just by reading the code. We didn’t chose Akka just for fun. It’s a business critical application that’s used in broadcasting.
Our Akka journey started as a prototyping project, but Akka has now become a crucial part of our system.
The Actor model has simplified a lot of concerns in the type of systems that we build and is now part of our reference architecture. With Akka we deliver systems that meet the most strict performance requirements of our clients in a near-realtime environment. We have found the Akka framework and it's support team invaluable.
Questions?
Persistors
Router
Behavior Generator
C*
C*
REST API
Input Queue
Output Queue
Persistors
Router
Behavior Generator
Log Converter
C*
- REST API
- Behavior Generation
- Persist Auth Info
- Log Content
C*
REST API
C*
Input Queue
Router
Log Converter
Persistors
- Log Content
C*
REST API
Input Queue
Output Queue
Persistors
Router
Behavior Generator
Log Converter
C*
- REST API
- Behavior Generation
- Persist Auth Info
- Log Content
Actors
By Colin
Actors
- 1,414