Actors, Akka, andYou
Dylan Forciea
Oseberg, Inc.
Topics
• Traditional concurrent programming and its issues
• Actors as a solution
• Let it crash!
• Referencing actors
• Where are actors deficient, and the next steps…
Traditional Concurrent Programming
• Traditional locking methodologies
• Mutex/semaphore
• Directly lock and unlock for mutually exclusive areas
• Monitor/condition (used in Java synchronization)
• Lock on a monitor
• Wait on a monitor or perform work and call notify to signal being done
• Waiting threads are notified and can continue from being locked
Problems
• Deadlock – A circular dependency of locks have been created causing you to wait
forever
• Livelock –Two threads reacting to each other’s monitors, and never getting
anywhere
• Starvation – One thread may take a long time to make progress due to another
thread taking too long
Thread 1 Thread 2
Thread 3 Thread 4
Dining Philosophers
How DoWe Simplify?
• Can we get rid of monitors and locks, or at the very least abstract them away?
• Yes, we can!
• Create a queue (called a mailbox) of messages to process
• Create an entity called anActor that will process those messages
• VERY IMPORTANT: Make sure that your messages are immutable, or else
you still need locking between the actors!
A LittleTheory…
• An actor can:
• Make local decisions – free to mutate internal state at will
• Send a finite number of messages to other actors
• Create a finite number of new actors
• Designate the behavior to be used for the next message it receives.
Merge Sort
• Divide array in half
• Recursively sort each half
• Merge sorted halves by iterating and picking least of each array
• Array of size 0 or 1 is already sorted (the base case)
59 4 7
59 4 7
9 5 4 7
5 9 4 7
4 5 7 9
Merge Sort Actor Design
Merger
Sorter
Sorter
Sorter
HeyWait,That Looks Familiar…
• It looks a lot like some of the message queueing middleware out there
• Or an ExecutorService on a local level…
• There are still some differences
• You can dynamically spin up child actors to perform more work easily
• You have a mechanism to perform failure recovery within the actor system
• You are free to perform the work locally or remotely (more on that later...)
Actors andThreads
• Uses thread pools
• Only need a thread as long as it takes to process a single message
• It is very important to not block in an actor
• You can break down work and send to child actors then aggregate the results
• For I/O, you may need to assign a separate thread pool just for those operations
Actors andThreads
• You are free to create LOTS of actors!
• Since they do not directly create a thread, and only have ~300 byte
overhead, you can have thousands or even millions simultaneously in a
system
• Creating lots of threads will:
• Need to allocate a thread stack, which is 512kb by default (expensive memory-wise)
• Require more context switching if there are many of them (expensive cpu-wise)
MailboxTypes
• Nonblocking
• UnboundedMailbox
• SingleConsumerOnlyUnboundedMailbox
• NonBlockingBoundedMailbox
• UnboundedControlAwareMailbox
• UnboundedPriorityMailbox
• UnboundedStablePriorityMailbox
• Blocking
• BoundedMailbox
• BoundedControlAwareMailbox
• BoundedPriorityMailbox
• BoundedStablePriorityMailbox
• Nonblocking means that a message send will always succeed
• Blocking means that upon timeout, a message send may fail
How Do Akka ActorsWork?
• An Actor derives from the AbstractActor class, and defines a createReceive method
public class MergeActor extends AbstractActor {
public Receive createReceive() {
return receiveBuilder()
.match(String.class, s -> getContext().sender().tell(“Hi, “ + s))
.build();
}
}
• You can match on a type of object coming in and perform some action on it
• You can also swap out your receive by using the become() method on your context
like a finite state machine
Referencing Actors
• Once you have an actor, you need to be able to find and reference it
• On Creation:
• ActorRef actorRef = actorSystem.actorOf(YourActor.props());
• You create a Props class describing the actor and its parameters
• It is considered good practice to create these in a static method in the actor class
• Lookup:
• Actors are created in a tree
• ActorRef actorRef = getContext().actorSelection("/actorA/actorB/actorC “);
• This means that actorC is a child of actorB, which is a child of actorA
• You can also use relative directory style paths, e.g. “../actorB1” when in actorC
• These return an ActorRef, not an Actor instance
• This is important later, when we see that there are local and remote actors
Messaging Paradigms
• Tell (fire and forget)
• actorRef.tell(“foo”, getContext().sender())
• Sends the string “foo” to an actor and continues on
• Ask
• PatternsCS.ask(myActor, ”foo”, timeout)
• This will immediately return and give you a CompletionStage object, which you can
describe more computations to be done when the original computation is done
• The receiving actor has a sender in its context to send a message back that will be used to
complete the ask
• You can also .get… but that is discouraged if avoidable since it ties up your thread
• Forward – Keep the same message sender and context and delegate
Let It Crash!
• Expect failure, and provide a means to recover from that failure in the actor
system
• Akka provides a mechanism for handling when an error occurs
• This helps with building robust systems that will keep going even upon
failure
Supervision Strategies
• Each actor supervises its children, and has the following options on failure:
• Resume the subordinate, keeping its accumulated internal state
• Restart the subordinate, clearing out its accumulated internal state
• Stop the subordinate permanently
• Escalate the failure, thereby failing itself
• Choice of two strategies upon failure:
• OneForOneStrategy – Only applies to child that failed
• AllForOneStrategy – Applies to ALL children when one fails
• You can determine the appropriate option based upon which exception was thrown
Supervision Strategies
public class Supervisor extends AbstractActor {
private static SupervisorStrategy strategy =
new OneForOneStrategy(10, Duration.create("1 minute"), DeciderBuilder
.match(ArithmeticException.class, e -> resume())
.match(NullPointerException.class, e -> restart())
.match(IllegalArgumentException.class, e -> stop())
.matchAny(o -> escalate()).build());
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
}
Router Actors
• Akka provides a variety of routing actors out of the box
• A Pool means the router creates its own actors, Group means it is supplied the actors
• Some examples:
• BalancingPool – Create multiple of an actor that share a mailbox
• RoundRobinPool – Send messages to a set of actors in a round robin fashion
• SmallestMailboxPool – Send message to one of a set of actors with the smallest mailbox
• BroadcastGroup – Send incoming messages to all actors in the group
• There are many more, and you can define your own
• The router actors can be defined in your configuration file
Remote Actors
• Actors can run on any number of different machines
• For Actors sending messages to an actor on another machine, the remote
Actor is abstracted away behind an ActorRef
• This means you must treat all actor calls as asynchronous and possibly having a long
response time
• You can even serialize an ActorRef and send it across the wire!
• You use the same getContext().actorSelection method as before:
• akka.<protocol>://<actor system name>@<hostname>:<port>/<actor path>
• Config file can specify where an actor at a given path is run
Remote Actors
• In order to serialize the messages, Akka provides support out of the box for
Google Protocol Buffers
• If you send a Google Protocol Buffers object between remote actors, it will
transparently serialize and deserialize the message over the wire
• From personal experience, it is a good idea to make all messages be
protocol buffers messages if you intend on having any remote actors
• You can freely move actors from one system to the next via configuration
• It enforces the immutability constraint on messages between actors even locally
Configuration
• Akka actors provide a rich system for configuring:
• The properties of mailboxes
• Routers for distributing workload
• Remote actor system locations
• Thread pools, assignments and sizes
• Many many more…
Any problems or concerns?
Why does Akka Streams Exist?
• If one actor is able to perform work much faster than an actor receiving
messages from it, then you have a few potential problems
• If using a nonbounded mailbox, you may get an OutOfMemoryError
• If using a bounded nonblocking mailbox, you may lose messages
• If using a bounded blocking mailbox, you may have an exception thrown for the sender
• We need what is called back-pressure
Back Pressure
• When a Flow in akka-streams processes messages, it can send demand
information to upstream actors so they can slow down if the downstream
actors are not keeping up
• This makes it so we do not overflow mailboxes along the way
• Actors out of the box DO NOT have this functionality!
• Unfortunately, akka-streams do not yet (or will ever?) have the ability to
work across machine boundaries like remote actors due to problems if
messages are lost
• This underlines the fact that remote actors have at-most-once delivery!
Any Questions?
References
• https://en.wikipedia.org/wiki/Actor_model
• http://doc.akka.io/docs/akka/current/java/index-actors.html
• http://www.codiply.com/blog/scala-akka-tutorial-merge-sort-with-actors/

Akka Actors

  • 1.
    Actors, Akka, andYou DylanForciea Oseberg, Inc.
  • 2.
    Topics • Traditional concurrentprogramming and its issues • Actors as a solution • Let it crash! • Referencing actors • Where are actors deficient, and the next steps…
  • 3.
    Traditional Concurrent Programming •Traditional locking methodologies • Mutex/semaphore • Directly lock and unlock for mutually exclusive areas • Monitor/condition (used in Java synchronization) • Lock on a monitor • Wait on a monitor or perform work and call notify to signal being done • Waiting threads are notified and can continue from being locked
  • 4.
    Problems • Deadlock –A circular dependency of locks have been created causing you to wait forever • Livelock –Two threads reacting to each other’s monitors, and never getting anywhere • Starvation – One thread may take a long time to make progress due to another thread taking too long Thread 1 Thread 2 Thread 3 Thread 4
  • 5.
  • 6.
    How DoWe Simplify? •Can we get rid of monitors and locks, or at the very least abstract them away? • Yes, we can! • Create a queue (called a mailbox) of messages to process • Create an entity called anActor that will process those messages • VERY IMPORTANT: Make sure that your messages are immutable, or else you still need locking between the actors!
  • 7.
    A LittleTheory… • Anactor can: • Make local decisions – free to mutate internal state at will • Send a finite number of messages to other actors • Create a finite number of new actors • Designate the behavior to be used for the next message it receives.
  • 8.
    Merge Sort • Dividearray in half • Recursively sort each half • Merge sorted halves by iterating and picking least of each array • Array of size 0 or 1 is already sorted (the base case) 59 4 7 59 4 7 9 5 4 7 5 9 4 7 4 5 7 9
  • 9.
    Merge Sort ActorDesign Merger Sorter Sorter Sorter
  • 10.
    HeyWait,That Looks Familiar… •It looks a lot like some of the message queueing middleware out there • Or an ExecutorService on a local level… • There are still some differences • You can dynamically spin up child actors to perform more work easily • You have a mechanism to perform failure recovery within the actor system • You are free to perform the work locally or remotely (more on that later...)
  • 11.
    Actors andThreads • Usesthread pools • Only need a thread as long as it takes to process a single message • It is very important to not block in an actor • You can break down work and send to child actors then aggregate the results • For I/O, you may need to assign a separate thread pool just for those operations
  • 12.
    Actors andThreads • Youare free to create LOTS of actors! • Since they do not directly create a thread, and only have ~300 byte overhead, you can have thousands or even millions simultaneously in a system • Creating lots of threads will: • Need to allocate a thread stack, which is 512kb by default (expensive memory-wise) • Require more context switching if there are many of them (expensive cpu-wise)
  • 13.
    MailboxTypes • Nonblocking • UnboundedMailbox •SingleConsumerOnlyUnboundedMailbox • NonBlockingBoundedMailbox • UnboundedControlAwareMailbox • UnboundedPriorityMailbox • UnboundedStablePriorityMailbox • Blocking • BoundedMailbox • BoundedControlAwareMailbox • BoundedPriorityMailbox • BoundedStablePriorityMailbox • Nonblocking means that a message send will always succeed • Blocking means that upon timeout, a message send may fail
  • 14.
    How Do AkkaActorsWork? • An Actor derives from the AbstractActor class, and defines a createReceive method public class MergeActor extends AbstractActor { public Receive createReceive() { return receiveBuilder() .match(String.class, s -> getContext().sender().tell(“Hi, “ + s)) .build(); } } • You can match on a type of object coming in and perform some action on it • You can also swap out your receive by using the become() method on your context like a finite state machine
  • 15.
    Referencing Actors • Onceyou have an actor, you need to be able to find and reference it • On Creation: • ActorRef actorRef = actorSystem.actorOf(YourActor.props()); • You create a Props class describing the actor and its parameters • It is considered good practice to create these in a static method in the actor class • Lookup: • Actors are created in a tree • ActorRef actorRef = getContext().actorSelection("/actorA/actorB/actorC “); • This means that actorC is a child of actorB, which is a child of actorA • You can also use relative directory style paths, e.g. “../actorB1” when in actorC • These return an ActorRef, not an Actor instance • This is important later, when we see that there are local and remote actors
  • 16.
    Messaging Paradigms • Tell(fire and forget) • actorRef.tell(“foo”, getContext().sender()) • Sends the string “foo” to an actor and continues on • Ask • PatternsCS.ask(myActor, ”foo”, timeout) • This will immediately return and give you a CompletionStage object, which you can describe more computations to be done when the original computation is done • The receiving actor has a sender in its context to send a message back that will be used to complete the ask • You can also .get… but that is discouraged if avoidable since it ties up your thread • Forward – Keep the same message sender and context and delegate
  • 17.
    Let It Crash! •Expect failure, and provide a means to recover from that failure in the actor system • Akka provides a mechanism for handling when an error occurs • This helps with building robust systems that will keep going even upon failure
  • 18.
    Supervision Strategies • Eachactor supervises its children, and has the following options on failure: • Resume the subordinate, keeping its accumulated internal state • Restart the subordinate, clearing out its accumulated internal state • Stop the subordinate permanently • Escalate the failure, thereby failing itself • Choice of two strategies upon failure: • OneForOneStrategy – Only applies to child that failed • AllForOneStrategy – Applies to ALL children when one fails • You can determine the appropriate option based upon which exception was thrown
  • 19.
    Supervision Strategies public classSupervisor extends AbstractActor { private static SupervisorStrategy strategy = new OneForOneStrategy(10, Duration.create("1 minute"), DeciderBuilder .match(ArithmeticException.class, e -> resume()) .match(NullPointerException.class, e -> restart()) .match(IllegalArgumentException.class, e -> stop()) .matchAny(o -> escalate()).build()); @Override public SupervisorStrategy supervisorStrategy() { return strategy; } }
  • 20.
    Router Actors • Akkaprovides a variety of routing actors out of the box • A Pool means the router creates its own actors, Group means it is supplied the actors • Some examples: • BalancingPool – Create multiple of an actor that share a mailbox • RoundRobinPool – Send messages to a set of actors in a round robin fashion • SmallestMailboxPool – Send message to one of a set of actors with the smallest mailbox • BroadcastGroup – Send incoming messages to all actors in the group • There are many more, and you can define your own • The router actors can be defined in your configuration file
  • 21.
    Remote Actors • Actorscan run on any number of different machines • For Actors sending messages to an actor on another machine, the remote Actor is abstracted away behind an ActorRef • This means you must treat all actor calls as asynchronous and possibly having a long response time • You can even serialize an ActorRef and send it across the wire! • You use the same getContext().actorSelection method as before: • akka.<protocol>://<actor system name>@<hostname>:<port>/<actor path> • Config file can specify where an actor at a given path is run
  • 22.
    Remote Actors • Inorder to serialize the messages, Akka provides support out of the box for Google Protocol Buffers • If you send a Google Protocol Buffers object between remote actors, it will transparently serialize and deserialize the message over the wire • From personal experience, it is a good idea to make all messages be protocol buffers messages if you intend on having any remote actors • You can freely move actors from one system to the next via configuration • It enforces the immutability constraint on messages between actors even locally
  • 23.
    Configuration • Akka actorsprovide a rich system for configuring: • The properties of mailboxes • Routers for distributing workload • Remote actor system locations • Thread pools, assignments and sizes • Many many more…
  • 24.
    Any problems orconcerns?
  • 25.
    Why does AkkaStreams Exist? • If one actor is able to perform work much faster than an actor receiving messages from it, then you have a few potential problems • If using a nonbounded mailbox, you may get an OutOfMemoryError • If using a bounded nonblocking mailbox, you may lose messages • If using a bounded blocking mailbox, you may have an exception thrown for the sender • We need what is called back-pressure
  • 26.
    Back Pressure • Whena Flow in akka-streams processes messages, it can send demand information to upstream actors so they can slow down if the downstream actors are not keeping up • This makes it so we do not overflow mailboxes along the way • Actors out of the box DO NOT have this functionality! • Unfortunately, akka-streams do not yet (or will ever?) have the ability to work across machine boundaries like remote actors due to problems if messages are lost • This underlines the fact that remote actors have at-most-once delivery!
  • 27.
  • 28.

Editor's Notes

  • #12 Since there is only a need to hold onto a thread for the period of time it takes to retrieve a message from the mailbox and process it, you can use a thread pool rather than a thread per actor
  • #17 Patterns rather than PatternsCS returns the scala Future object. But PatternsCS for Java 8 will wrap it in a CompletionStage object which is standard to Java.