From 35009f6626c798006021e2b25a6cad7b77a33e56 Mon Sep 17 00:00:00 2001 From: Henrik Kirk Date: Tue, 19 Nov 2024 12:17:20 +0100 Subject: [PATCH] Changes to Akka-II --- slides/12/actor-2.md | 240 +++++++++++++++++++++---------------------- slides/12/index.html | 17 +++ 2 files changed, 133 insertions(+), 124 deletions(-) diff --git a/slides/12/actor-2.md b/slides/12/actor-2.md index e482eea..082dfce 100644 --- a/slides/12/actor-2.md +++ b/slides/12/actor-2.md @@ -8,26 +8,14 @@ ### Agenda * Actor lifecycle -* ActorSelection +* Actor selection +* Good design practices * State machines -* Router +* Router actors --- -TODO: insert - -#### Actor path - -* Every actor has an actor path -* Actor path can be used to send messages to an actor - -![Actor path](./img/actor_path.png) - ---- - - ---- ### Actor lifecycle @@ -42,43 +30,41 @@ Actor have 5 stage life cycle #### Lifecycle stages -* `Starting`: Actor is waking up -* `Recieving`: Actor is accepting messages -* `Stopping`: Actor is cleaning up its state, or saving state if restarting -* `Terminated`: Actor is dead -* `Restarting`: Actor is about to restart +* Starting: Actor is waking up
+* Recieving: Actor is accepting messages
+* Stopping: Actor is cleaning up its state, or saving state if restarting
+* Terminated: Actor is dead
+* Restarting: Actor is about to restart
---- #### Lifecycle hooks -* `PreStart`: Run before receiving - used to initialize -* `PreRestart`: used to cleanup before restart -* `PostStop`: Called when actor has stopped recieving. Cleanup - called as part of `PreRestart` -* `PostRestart`: Called after `PreRestart` and before `PreStart`. Additional reporting/diagnosis +* PreStart: Run before receiving - used to initialize +* PreRestart: used to cleanup before restart +* PostStop: Called when actor has stopped recieving. Cleanup - called as part of `PreRestart` +* PostRestart: Called after `PreRestart` and before `PreStart`. Additional reporting/diagnosis ---- -#### How to do you do this in F# +### classes in F# -Method 1) +```fsharp [1-2, 7-8] +type PlaybackActor() = + inherit UntypedActor() -```fsharp -let preStart = Some(fun () -> - Console.WriteLine "preStart Called" - |> ignore) -let mySampleActor = spawnOvrd system "actor" - (actorOf sampleActor) <| - {defOvrd with PreStart = preStart} + override __.OnReceive message = + // TODO + + override __.PreStart() = + // do something, like logging for example ``` ---- #### Only for `PreStart` and/or `PostStop` -Method 2) - ```fsharp let sampleActor (mailbox:Actor<_>) = // this section works like pre-start @@ -88,7 +74,7 @@ let sampleActor (mailbox:Actor<_>) = let rec loop () = actor { let! msg = mailbox.Receive () - // do some work + // TODO handle msg return! loop () } loop () @@ -96,38 +82,54 @@ let sampleActor (mailbox:Actor<_>) = let aref = spawn system "actor" (sampleActor) ``` -Note: - -This syntax is called a computation expresion - we will briefly touch on this in week 13/14 - otherwise the HR chapter 12. --- ### ActorSelection -* Used to send messages to actor(s) to which you don't have an `IActorRef` -* Done by actor path +* Every actor has an actor path
+* Actor path can be used to send messages to an actor
+* Used to send messages to actor(s) to which you don't have an IActorRef
+ -![Actor Path](./img/actor_path.png) +![Actor Path](./img/actor_path.png) ---- #### ActorSelection details -* ActorSelection +* ActorSelection
* The processs of looking up an actor * The object returned from that lookup -* ActorSelection don't give you a 1:1 relationship -* ActorSelection give you a handle to all actors behind that path +* ActorSelection don't give you a 1:1 relationship
+* ActorSelection give you a handle to all actors behind that path
* e.g. wildcards are supported + +---- + +#### Creating an actor path + +```fsharp +let selection = select "path/to/actor" mailbox.Context.System + +// e.g. +let myFooActor = spawn actorSystem "barActor" + (actorOf fooActor) +// in an actor +let selection' = select "akka://user/barActor" + mailbox.Context.System +selection' +* Loose coupling
+* Dynamic behavior
+* Adaptive system design
---- @@ -141,80 +143,65 @@ This syntax is called a computation expresion - we will briefly touch on this in #### Loose coupling -* Don't need to store and/or pass around `IActorRef` -* Less coupling between actors/components +* Don't need to store and/or pass around IActorRef
+* Less coupling between actors/components
---- #### Dynamic behavior -* In a dynamic system where actors are created/removed +* In a dynamic system where actors are created/removed
* you can send messages to known addresses -* Don't need to be hardcoded adresses, messages can represent actorPath +* Don't need to be hardcoded adresses, messages can represent actorPath
---- #### Adaptive system design -* Help when buidling actor system -* You can introduce new actors/sections without going back and changing the existing system +* Help when buidling actor system
+* You can introduce new actors without going back and changing the existing system
---- #### When to use -1. Talking to top level actors +1. Talking to top level actors * E.g you have an `AuthenticateActor` which name is `/user/AuthenticateActor` ```fsharp select "akka://MyActorSystem/user/AuthenticateActor" mailbox.Context.System -2. Handoff work to a pool of worker actors -3. When processing a message and `Sender` is not enough -4. Send to multiple actors +2. Send to multiple actors +3. When processing a message and 'Sender' is not enough
+4. Handoff work to a pool of worker actors
---- -#### Don't send `ActorPaths` around +#### Take care when passing `ActorPaths` around Because: * ActorSelection can be relative ----- - -#### Creating an actor path - -```fsharp -let selection = select "path/to/actor" mailbox.Context.System - -// e.g. -let myFooActor = spawn actorSystem "barActor" - (actorOf fooActor) -// in an actor -let selection' = select "akka://user/barActor" - mailbox.Context.System -selection' +* Always communicate via top-level actors
+* Delegate risky operations to leafs
---- ### Knowledge -* Make it possible to change implementation details (DIP, LSP) -* Top-level actors are interfaces +* Make it possible to change implementation details (DIP, LSP)
+* Use top-level actors as interfaces
-![Limit knowledge](./img/limit-knowledge-about-cousins.png "Limit knowledge") +![Limit knowledge](./img/limit-knowledge-about-cousins.png "Limit knowledge")
---- @@ -223,10 +210,10 @@ selection' * by `IActorRef` or `ActorSelection` -* Make extension possible (OCP) -* Return is possible with `IActorRef.Forward` +* Makes extension possible (OCP)
+* Return is possible with IActorRef.Forward
--- @@ -239,12 +226,12 @@ selection' +* Many state machines have some sort of time perspective
* change state after some time * stay in a state for some time -\* called `become` in C# and Scala +\* called become in C# and Scala ---- @@ -273,10 +260,10 @@ let actor (mailbox: Actor<_>) = #### Different states -* In the above example our actor can handle messages base on 3 states +* In the above example our actor can handle messages base on 3 states
* `autenticating`, `unauthenticated` and `authenticated` -* This enables reusablitity, and different work with little code -* Safe because we only handle one messages at a time. +* This enables reusablitity, and different work with little code
+* Safe because actors only handle one messages at a time.
---- @@ -284,7 +271,7 @@ let actor (mailbox: Actor<_>) = Let us imagine a chat example: -```fsharp +```fsharp [1-13|7|14-25|6|26-36 ] let rec authenticating () = actor { let! message = mailbox.Receive () @@ -343,10 +330,10 @@ let actor (mailbox: Actor<_>) = #### Unhandled messages -* Actors has a `Stash` which acts like stack structure +* Actors has a Stash which acts like stack structure
* Calling `mailbox.Stash ()` puts current message onto stack -* `mailbox.Unstash ()` puts the top message at the front of the inbox -* `mailbox.UnstashAll ()` unstashed all messages. +* mailbox.Unstash () puts the top message at the front of the inbox
+* mailbox.UnstashAll () unstashes all messages.
* Preserves FIFO order @@ -354,7 +341,7 @@ let actor (mailbox: Actor<_>) = #### Unstash in our example -```fsharp +```fsharp[9-12|5,7] let rec authenticating () = actor { let! message = mailbox.Receive () @@ -375,49 +362,53 @@ let rec authenticating () = #### Stashed messages -* Keep states -* In case of restart +* Keep states
+* In case of restart
* Stash is lost with actor state - * Unstash all before restart can save messages in mailbox + * Unstash all before restart will save messages in mailbox -```fsharp -let preRestart = Some(fun (basefn: exn * obj -> unit) - -> mailbox.UnstashAll () |> ignore) -let mySampleActor = spawnOvrd system "actor" - (actorOf sampleActor) - <| { defOvrd with PreRestart = preRestart } +```fsharp [2] +let disposableActor (mailbox:Actor<_>) = + mailbox.Defer (fun () -> mailbox.UnstashAll ()) + let rec loop () = + actor { + let! msg = mailbox.Receive() + return! loop () + } + loop() ``` + --- ### Router -* Special actor types (Pool or Group) -* Can handle multiple messages at a time (Whuut!) -* Router actors can only forward messages (puh) -* Routers can be configured programaticly or using conf files +* Special actor types (Pool or Group)
+* Can handle multiple messages at a time (Whuut!)
+* Router actors can only forward messages (puh)
+* Routers can be configured programaticly or using conf files
---- #### Pool Router -* Owns (supervise) their routee children -* Makes it possible to control pool size +* Owns (supervise) their routee children
+* Makes it possible to control pool size
---- #### Routing strategies -* `Broadcast` - Forward to all routees -* `Random` - Forward to a random routee -* `ConsistentHash` - Forward to a specific routee based on message +* Broadcast - Forward to all routees
+* Random - Forward to a random routee
+* ConsistentHash - Forward to a specific routee based on message
---- #### Supervision -* Router are actors so we can used `SupervisorStrategy` -* Default is 'always escalate' +* Router are actors so we can used SupervisorStrategy
+* Default is 'always escalate' ---- @@ -461,20 +452,20 @@ akka.actor.deployment { Try to evenly distribute work -* `RoundRobin` - just forward each message to a new routee -* `TailChooping` - forward to one random routee, after some delay send to a new until answer is recieved. -* `ScatterGatherFirstCompleted` - forward to all routees and return first answer. If no answer within a given timeframe, reply to sender. +* RoundRobin - just forward each message to a new routee
+* TailChooping - forward to one random routee, after some delay send to a new until answer is recieved.
+* ScatterGatherFirstCompleted - forward to all routees and return first answer. If no answer within a given timeframe, reply to sender.
---- #### Load balacing 2 (Pool only) -* `Resizable` - Forward to routee with fewest messages in mailbox +* Resizable - Forward to routee with fewest messages in mailbox 1. Idle routee 2. Routee with empty mailbox 3. Routee with fewest messages 4. Remote routee -* `ResizableRouter` - 'Auto scaling', tries to detect pressure on routee and expand/contract pool size +* ResizableRouter - 'Auto scaling', tries to detect pressure on routee and expand/contract pool size ---- @@ -495,10 +486,10 @@ router Do not supervise the routee * ActorRefs are handed to the Group Router -```fsharp +```fsharp [10-11] // Create some routee actors let c1 = spawn mailbox.Context "coordinator1" (coordinatorA) let c2 = spawn mailbox.Context "coordinator2" (coordinatorA) @@ -511,6 +502,7 @@ let coordinatorPaths = let coordinator = mailbox.Context.ActorOf( Props.Empty.WithRouter(BroadcastGroup(coordinatorPaths))) ``` + ---- @@ -519,7 +511,7 @@ let coordinator = mailbox.Context.ActorOf( * Use Pool actor unless 1. Router should be able to send via `ActorSelection` 2. You router cannot (for some reason) be responsible for routees - 3. You need to be able to send to different actors + 3. You need to be able to send to different types of actors --- diff --git a/slides/12/index.html b/slides/12/index.html index d348397..526e008 100644 --- a/slides/12/index.html +++ b/slides/12/index.html @@ -17,6 +17,23 @@ +