fsharp-akka-talk



fsharp-akka-talk

1 5


fsharp-akka-talk

Akka.NET with F# - Getting functional with Reactive systems

On Github russcam / fsharp-akka-talk

Akka.NET with F# - @forloop

Akka.NET with F#

Getting functional with Reactive systems

Russ Cam @forloop

About me

  • Independent Software Engineer
  • Interested in
    • Distributed systems
    • Cloud computing
    • Message-driven architectures
    • Software Design, Patterns and Practices
  • Holla!

My First Computer

The Acorn Electron

  • Budget BBC Micro
  • Released in 1983 (31 years ago)
  • Audio cassette tapes!
  • 32 kB RAM

Classic titles

Arguably the Grand Theft Auto of its generation, albeit with a hippy twist.

WELCOME TO THE FUTURE!

Agent-based Programming

a.k.a. The Actor model

...is a mathematical model of concurrent computation that treats "actors" as the universal primitives of concurrent computation: in response to a message that it receives, an actor can make local decisions, create more actors, send more messages, and determine how to respond to the next message received.

First proposed in 1973!

  • Actor architectures are not a new idea
  • 1973 - Carl Hewitt, Peter Bishop and Richard Steiger
  • paper "A universal modular ACTOR formalism for artificial intelligence"

Sequential flow

  • Single control flow
  • One entry point
  • Other components called
  • Control proceeds until result is returned

Parallel flow

  • Work that can be split up is done so using multiple threads/tasks to perform it in parallel
  • Improved throughput over sequential model
  • Coordination model not indifferent from sequential
  • Work can be concurrent, but proceeds until result is returned

Agent-based (Actor) flow

  • Communication through asynchronous message passing
  • Non-blocking
  • Reactive system - may be less well defined entry and exit points

Ericsson AXD 301

  • Erlang
  • 99.9999999% uptime - "9 Nines"
  • over 20 years!
  • ~0.631 secs downtime per year
  • well known example of an Actor architecture is the Ericsson AXD 301
  • Asynchronous Transfer Mode (ATM) switching system to unify telecommunication and computer networks
  • ERLANG
  • 9 nines = service uptime, NOT uptime of computers running the system

akka.net

  • Port of the JVM Akka Toolkit to the .NET Framework
  • Heavily influenced by the Actor model implementation in Erlang
  • Licensed under Apache 2
  • Let it crash philosophy
  • Named after Swedish mountain in region of Laponia.

Who's using akka?

Investment and Merchant Banking Retail Social Media Simulation Gaming and Betting Automobile and Traffic Systems Data Analytics

Reactive Manifesto

Why do we need a Reactive Manifesto?

  • Akka adheres to the Reactive Manifesto
  • The requirements for applications today have changed
  • Users expect millsecond response times
  • The system may be distributed over multiple cores and machines

Message Passing

  • Asynchronous message passing to establish a boundary between components
  • Loose Coupling
  • Isolation
  • Location Transparency
  • Delegate Errors as messages

Resilient

  • Stay Responsive in face of failure
  • Contain failures to specific portions of system

Elastic

  • React to the workload exerted on the system
  • Scalable up and out

Responsive

  • Respond in timely manner, if at all possible
  • Builds user confidence in the system

Anatomy of an actor

Objects that encapsulate state and behaviour, communicating exclusively through message passing

Can do 4 things

  • Receive and respond to incoming messages
  • Send messages to other actors
  • Create new actors
  • Change behaviour in response to an incoming message

In Akka, pretty much everything is an actor

  • Loggers
  • Routing
  • Persistence
  • Eventbus
  • Default Lifecycle monitoring

Who creates the first Actor?

Actor System

  • Create top level actors
  • Heavyweight structure, so create one per logical applicaytion
  • Use System.create as it sets up serialization support for Remote Deployment
  • EXAMPLE 0_ActorSystem.fsx
  • Error Kernel Pattern
  • Fault Tolerance by pushing the most error prone operations to the edges of the actor hierarchy

Creating Actors

types

type EchoActor() =
    inherit UntypedActor()
        override this.OnReceive (msg:obj) = 
            printfn "Received message %A" msg               
                        
let system = System.create "system" <| Configuration.defaultConfig()
                        
// Use Props() to create actor from type definition
let echoActor = system.ActorOf(Props(typedefof<EchoActor>), "echo")

// tell a message
echoActor <! "Hello World!"

How very C# like!

Let's get functional

actorOf

Receives next message from mailbox

let echo (msg:obj) =
    printfn "Received message %A" msg
                            
let system = System.create "system" <| Configuration.defaultConfig()
                        
// use spawn in conjunction with actorOf.
// Wraps provided function to give actor behaviour
let echoActor = spawn system "echo" (actorOf echo)
                        
// tell a message
echoActor <! "Hello World!"

actorOf2

Receives "self" actor and next message from mailbox

let echo (mailbox:Actor<'a>) msg =
    printfn "Received message %A" msg
                            
let system = System.create "system" <| Configuration.defaultConfig()
                        
// use spawn in conjunction with actorOf2.
// Wraps provided function to give actor behaviour
let echoActor = spawn system "echo" (actorOf2 echo)
                        
// tell a message
echoActor <! "Hello World!"

actor computation expression

Receives "self" actor and returns a Continuation

let echo (mailbox:Actor<'a>) =
    let rec loop () = actor {
        let! msg = mailbox.Receive ()
        printfn "Received message %A" msg
        return! loop ()
    }
    loop ()
                            
let system = System.create "system" <| Configuration.defaultConfig()
                        
// use spawn in conjunction with actor computation expression
let echoActor = spawn system "echo" echo
                        
// tell a message
echoActor <! "Hello World!"
  • self-invoking recursive function
  • type Cont<'In, 'Out> = | Func of ('In -> Cont<'In, 'Out>) | Return of 'Out
  • ActorType
  • ActorOf
  • ActorOf2
  • Props
  • EXAMPLE 1_ActorType.fsx
  • EXAMPLE 2_ActorOf.fsx
  • EXAMPLE 2_ActorOf2.fsx
  • EXAMPLE 3_ActorComputationExpression.fsx

I can create actors, now what?

Message passing

ActorRef

// ActorRef
let echoActorRef = spawn system "echo" 
                       (actorOf (fun m -> printfn "received %A" m))
                        
// tell a message to actor ref
echoActorRef <! "Hello World!"
  • ActorSystem actually returns an ActorRef to the actor
  • ActorRef is essentially an address to a particular actor
  • Location transparency

ActorSelection

// ActorSelection, using wildcard actor path
let echoActorSelection = select "/user/echo*" system
                        
// tell a message to actor selection
echoActorSelection <! "Hello World!"
  • Location Transparency
  • ActorSelection could be more than one ActorRef
  • Prefer ActorRef where possible. Object to communicate with an actor.
  • ActorRef can be passed around in messages, etc.
  • ActorRef examples:

Tell

One way, Fire & Forget

actor <! message
  • Send a message, no reply expected
  • "Fire and Forget"
  • No blocking waiting for reply
  • Passing messages via ActorRefs
  • Passing messages via ActorSelection

Ask

Request / Response

async {
    let! response = actor <? message
    // do something with response
} |> Async.RunSynchronously
  • Send a message, expects a reply back
  • Does not block as it's an async method, but can timeout (basically a task)
  • Tell method is preferred because it offers better scaling and concurrency
  • Overhead to Ask involves the ActorSystem creating temporary actors to handle asynchonicity/li>
  • Ask should be preferred largely if the constraints of the system prevent the use of tell e.g. May be working with a web framework that does not allow Tell

Forward

actorToForwardTo.Forward message
  • Forward is similar to Tell with the primary difference being that the original sender address and reference are preserved.
  • Useful for when working with routers, load balancers, replicators and supervisors

PipeTo

async { 
    // some async workflow 
} |!> myActor

or

myActor <!| async { 
               // some async workflow 
            }
  • When running an async workflow/task, can pipe the response to an actor
  • Right now, there is a limitation in the F# API whereby the sender of the message piped with PipeTo is NoSender rather than the actor that encapsulated the async workflow. This is going to be addressed in future.

What about that hierarchy of actors?

Supervision

  • Examples
  • Strategies
type CustomException() = 
    inherit Exception()

type Message = 
    | Echo of string 
    | Crash

let system = System.create "system" <| Configuration.defaultConfig()
let child (childMailbox:Actor<Message>) = 
    let rec childLoop() = actor {
        let! msg = childMailbox.Receive()
        match msg with
        | Echo info -> 
            let response = 
                sprintf "Child %s received: %s" 
                    (childMailbox.Self.Path.ToStringWithAddress()) info
            childMailbox.Sender() <! response
        | Crash -> 
            printfn "Child %A received crash order" 
                (childMailbox.Self.Path)
            raise (CustomException())
        return! childLoop()
    }
    childLoop()
let parent (parentMailbox:Actor<Message>) =
    // parent actor spawns child - is the supervisor of the child
    let child = spawn parentMailbox "child" child

    let rec parentLoop() = actor {
        let! (msg: Message) = parentMailbox.Receive()
        child.Forward msg  
        return! parentLoop()
    }
    parentLoop()
// spawn with options to define a Supervisor Strategy
let parentChild = 
    spawnOpt system "parent" parent <| 
        [ SpawnOption.SupervisorStrategy (
            Strategy.OneForOne(fun e ->
                match e with
                | :? CustomException -> Directive.Restart 
                | _ -> SupervisorStrategy.DefaultDecider(e))); ]

...the one and only

...and one for all

The AllForOneStrategy is applicable in cases where the ensemble of children has such tight dependencies among them, that a failure of one child affects the function of the others, i.e. they are inextricably linked. Since a restart does not clear out the mailbox, it often is best to terminate the children upon failure and re-create them explicitly from the supervisor (by watching the children’s lifecycle); otherwise you have to make sure that it is no problem for any of the actors to receive a message which was queued before the restart but processed afterwards.
  • Escalate (default supervisor directive) - thereby failing itself
  • Resume - keeping its accumulated internal state
  • Restart - clearing out its accumulated internal state
  • Stop - permanently

Lifecycle Monitoring

a.k.a. Deathwatch

  • Monitor the lifecycle of another actor
  • Know when it is terminated
monitor (targetActor:IActorRef, watcher:ICanWatch)

and

demonitor (targetActor:IActorRef, watcher:ICanWatch)

Creating groups of similar actors?

Routers

Pools and Groups

  • RoundRobin
  • Broadcast
  • Consistent Hashing
  • TailChopping
  • ScatterGatherFirstCompleted
  • SmallestMailbox
  • Random
  • Broadcast - send the same message to all routees
  • Consistent Hashing - send messages with same "hash key" to same routee
  • Tail Chopping - send message to one, wait a little, send to another, return first, discard rest

Scaling out?

Akka Remote

  • Needs to be enabled on both systems
  • HOCON (Human-Optimized Config Object Notation)

Shared assemblies

let config = Configuration.parse """
akka {  
    actor {
    provider = 
        "Akka.Remote.RemoteActorRefProvider, Akka.Remote"
    deployment {
        /my-actor {
        remote = "akka.tcp://system2@localhost:8080"
        }
    }
    }
    remote {
    helios.tcp {
        transport-class = 
        "Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote"
	    applied-adapters = []
		transport-protocol = tcp
		port = 8090
		hostname = localhost
    }
    }
}"""
let system = System.create "system" <| config
let myActor = system.ActorOf<SomeActor>("my-actor")
  • Build an ActorSystem across several processes

F# Quotations

let system = System.create "system" <| Configuration.defaultConfig()
let myActor = spawne system "my-actor"
    <| (<@ fun mailbox ->                     
            printfn "Actor pre start"
            mailbox.Defer (fun () -> printfn "Actor post stop")
            let rec loop () =
                actor {
                    let! msg = mailbox.Receive ()
                    // msg handling here
                    return! loop ()
                }
            loop () @>) 
            [SpawnOption.Deploy(
                 Deploy(RemoteScope (Address.Parse "akka.tcp://system2@localhost:8080")))]
  • Serialized using FsPickler and sent across the wire (System.create adds FsPickler support)
  • No need to stop your remote nodes to reload shared actor assemblies when updated
  • DISADVANTAGE: Code embedded inside quotation must use only functions, types and variables known to both endpoints. There are limited ways to define functions inside quotation expression (and no way to define types), but generally speaking in most cases it’s better to define them in separate library and share between nodes

Changing Actor Behaviour?

FSM

Finite State Machine

let actor = spawn system "MyActor" <| 
                fun mailbox ->
                    let rec loop1() = actor {
                            let! message = mailbox.Receive()
                            // do something with message
                            return! loop2()
                        }
                    and loop2() = actor {
                            let! message = mailbox.Receive()
                            return! loop3()
                        }
                    and loop3() = actor {
                            let! message = mailbox.Receive()
                            return! loop1()
                        }
                    loop1()
  • Way to change the behaviour of actors at runtime
  • In response to messages

EXAMPLES

No time for...

Dispatchers

Mailboxes

Event stream/bus

Scheduler

Clustering

Testing

Persistence/Snapshots

Books

Online Resources

Jump in!

Thanks!

Addendum

What about TPL Dataflow?

MSDN documentation on TPL Dataflow
  • Not distributed - In process only
  • Need to be directly referenced
  • Cannot be remote deployed

What about MailboxProcessor<`a>?

Messages and Actors - F# for fun and profit
#nowarn "40"
let printerAgent = MailboxProcessor.Start(fun inbox-> 
        // the message processing function
        let rec messageLoop = async {     
                // read a message
                let! msg = inbox.Receive()
        
                // process a message
                printfn "message is: %s" msg
                // loop to top
                return! messageLoop  
            }
        // start the loop 
        messageLoop 
    )
  • Need to directly reference mailboxes
  • Built on top an unbounded queue
  • Not distributed
  • Cannot be remote deployed
  • Strongly typed mailbox - Pro and Cons
  • Some challenges in being consumed from C# e.g. Example 1 Example 2

What about Orleans?

Orleans: Distributed Virtual Actors for Programmability and Scalability
  • Higher level abstraction than Akka
  • Lack of support of Become/Unbecome (FSM)
  • Virtual Actors
  • Grains do not have lifecycles and created automatically when needed.
  • Focused on cons
  • No control over actor placement and load balancing
  • Comparison with Orleans

Event Stream/Bus

  • Provides a pub/sub model for events
  • Useful for side effects to main control flow i.e. no tight coupling
  • Originally conceived as a way to send messages to a group of Actors
  • Does not preserve the original sender of the message
  • EXAMPLE 11_EventStream

Scheduling

  • Need to make things happen in the future
  • Provided by the Actor System
  • Exact timing inexact
  • Cancellable via CancellationToken
Akka.NET with F# Getting functional with Reactive systems Russ Cam @forloop