Backday Xebia : Akka, the reactive toolkit

63
#backdaybyxebia FABIAN GUTIERREZ XAVIER BUCCHIOTTY Construire le SI de demain Akka, the reactive toolkit

Transcript of Backday Xebia : Akka, the reactive toolkit

#backdaybyxebia

FABIAN GUTIERREZXAVIER BUCCHIOTTY

Construire le SI de demain

Akka, the reactive toolkit

#backdaybyxebia

Speakers

@FabGutierr @xbucchiotty

#backdaybyxebia

Agenda● Concepts● Reactive Application● Actor Basics● Actor Supervision● Scale OUT● Use case

#backdaybyxebia

Concepts

#backdaybyxebia

Concurrency"Two or more tasks are making progress even though they might not be executing simultaneously"

Parallelism"The execution of multiple things is truly simultaneous"

Rob Pike - 'Concurrency Is Not Parallelism': https://vimeo.com/49718712

#backdaybyxebia

Synchronous"A method call is considered synchronous if the caller cannot make progress until the method returns a value or exception"

Asynchronous"Async call allows the caller to progress after a finite number of steps and the completion may be signaled via a callback, Future or message"

#backdaybyxebia

Blocking"When the delay of one thread can indefinitely delay some of the other threads"

Non blocking"No thread is able to indefinitely delay others"

#backdaybyxebia

Reactive Application

#backdaybyxebia

RUsers = REvents + RLoad + RFailure

React to events, pushing rather than pullingReact to load, focus on scalability rather than single-user performanceReact to failure, resilient systems with the ability to recover quickly at all levels

#backdaybyxebia

#backdaybyxebia

- Áhkká

All the wisdom and beauty of the world according to the sami mythology

A mountain in Laponia in the north part of Sweden

#backdaybyxebia

ProblemIt is very difficult to build:

● Correct highly concurrent systems

● Truly scalable systems● Self-healing, fault-

tolerant systems

GoalMake simpler:

● Concurrency● Scalability● Fault-tolerance

#backdaybyxebia

Scale UPis the same thing as

Scale OUT-- Jonas Bonér

#backdaybyxebia

Actor Basics

#backdaybyxebia

A computational model that embodies:● Processing● Storage● Communication

Actor Model

Carl Hewitt

#backdaybyxebia

"The Actor model allows the developer to write concurrent and distributed systems by abstracting the low-level problems of having to deal with locking and thread management"

Actor Model

#backdaybyxebia

● Lightweight objects● Thread-safe● Send and receive

messages

Actors

#backdaybyxebia

● Actors have a mailbox, all messages received go there

● When a message is received they decide what to do based on message type and content

Actor Behaviour● In addition to normal

operations, they can create new actors or send messages to other actors

● Messages can only be sent to actors whose address is known

#backdaybyxebia

Behaviour

State

Actor

Event-drivenThread

#backdaybyxebia

Defining Actorscase object Tick

class MyCounterActor extends Actor{ var counter = 0

def receive = { case Tick => counter += 1 println(counter) }}

val config = ConfigFactory.

load()

val system = ActorSystem("system", config)

val counterRef = system.actorOf(

Props[MyCounterActor],

"MyActor")

#backdaybyxebia

Sending Messages to ActorscounterRef ! Tick

counterRef tell Tick

counterRef.tell(Tick)

counterRef.tell(Tick, senderRef)

implicit val timeout =

Timeout(5 seconds)

val future = actor ? message//orval future = ask (actorRef, Tick, 5 seconds)

#backdaybyxebia

Become / Unbecomeclass HotSwapActor extends Actor with Stash { import context._ def angry: Receive = { case "foo" => sender() ! "I am already angry?" case "bar" => become(happy) }

def happy: Receive = { case "bar" => sender() ! "I am already happy :-)" case "foo" => become(angry) }

def receive = { case "foo" => become(angry) case "bar" => become(happy) }}

The Stash trait enables an actor to temporarily stash away messages that can not or should not be handled using the actor's current behavior

#backdaybyxebia

Actor Paths and Addresses

#backdaybyxebia

Actor Selectionval actorRef = system.actorFor( "/MyActor/A")

val parent = system.actorFor( "..")

val sibling = system.actorFor( "../B")

val refPath: ActorPath = actorRef.path

val selection =

system.actorSelection( "/MyActor/*")

selection ! "hello there"

#backdaybyxebia

Actor Supervision

#backdaybyxebia

Supervision = Parenting

#backdaybyxebia

The guardians

#backdaybyxebia

The Guardians = Top level Supervisors

/user, the guardian actorParent of all user-created actors

/system, the system guardianAllows an ordered shut-down sequence

/, the root guardianSupervises all top level and special actors

/user /system

/ You don’t interact with this other root actors as a user

Root Actor

#backdaybyxebia

● When?the actor is in corrupt internal state

Restarting

● How is it done?By creating a new instance of the actor and keeping the previous mailbox

#backdaybyxebia

Lifecycle monitoring or DeathWatch● An actor can monitor any other actor in order to

react to his termination

● It means that supervision reacts to failure

● Each actor is the supervisor of its children, and as such each actor defines fault handling strategy

#backdaybyxebia

Fault Tolerance(...or let it crash)

When an actor fails…

the supervisor will know what to do!!

#backdaybyxebia

Default Supervision (in parent actors)final val defaultStrategy: SupervisorStrategy = {

def defaultDecider: Decider = {

case _: ActorInitializationException => Stop

case _: ActorKilledException => Stop

case _: Exception => Restart

}

OneForOneStrategy()(defaultDecider)

}

You don’t have to specify your own supervisor strategy in each and every actor.

However, the default strategy looks like this:

#backdaybyxebia

Defining a supervisor strategyclass MyActorSupervisor extends Actor {

override val supervisorStrategy = OneForOneStrategy ( maxNrOfRetries = 10, withinTimeRange = 1 minute) { case _: ArithmeticException => Resume case _: NullPointerException => Restart case _: IllegalArgumentException => Stop case _: Exception => Escalate }

}

#backdaybyxebia

No idea what to do?Ask your supervisor!!

override val supervisorStrategy = OneForOneStrategy ( maxNrOfRetries = 10, withinTimeRange = 1 minute) { case _: ArithmeticException => Resume case t => super.supervisorStrategy.decider .applyOrElse(t,(_: Any) => Escalate) }

One-For-One All-For-One

OneForOneStrategy AllForOneStrategy

Default strategy Recommended when there are tight dependencies among children

Applies directives only to the failed child Applies directives to the failed child and siblings

#backdaybyxebia

Manage Failure From Within

class FaultTolerantService extends Actor {

override def preRestart( reason: Throwable, message: Option[Any]) = { // clean up before restart }

override def postRestart(reason: Throwable) = { // init after restart } ...}

Lifecycle hooks:● preStart● postStop● preRestart● postRestart

#backdaybyxebia

Scale UP

#backdaybyxebia

RoutingRedirects messages● RoundRobin● Random● SmallestMailbox● Broadcast● custom

val routerRef = system.actorOf(Props[Counter].withRouter( RoundRobinRouting( nrOfInstances = 5)))

akka.actor.deployment { /parent/router1 { router = round-robin-pool nr-of-instances = 5 }}

#backdaybyxebia

Scale OUT

#backdaybyxebia

#backdaybyxebia

Remote communicationIf no deployment configuration exist then the actor is deployed as local...it implies:● Write as local● Deploy as distributed in the cloud● No code change

#backdaybyxebia

Akka RemoteCommunication between systems is symmetricif a system A can connect to a system B then system B must also be able to connect to system A independently

The role of the communicating systems are symmetric in regards to connection patternsThere is no system that only accepts connections, and there is no system that only initiates connections

#backdaybyxebia

akka { actor { provider = "akka.remote.RemoteActorRefProvider" } remote { enabled-transports = [ "akka.remote.netty.tcp"] netty.tcp { hostname = "127.0.0.1" port = 2552 } }}

val selection = context.actorSelection( "akka.tcp://[email protected]:2552/user/MyActor")

selection ! "Pretty awesome feature"

#backdaybyxebia

Akka Clustering● Fault-tolerant● Decentralized● Peer-to-Peer

Cluster membership service no single point of failure or single point of bottleneck

#backdaybyxebia

Akka Clustering/producer/router { router = round-robin nr-of-instances = 100 cluster { enabled = on routees-path = "/user/routee" allow-local-routees = on }}

#backdaybyxebia

Failure Detection● Hashes the node ring● Picks 5 nodes● Request/Reply

heartbeat

Network Partition● Failure detector can mark an

unavailable member unreachable● If one node is unreachable then

no cluster convergence● This means that the leader

cannot perform its duties● A member can become

reachable again

#backdaybyxebia

Testing Actorsakka-testkit because JUnit is not enough

#backdaybyxebia

Big Questions!● Testing async calls without

using threads explicitly● You are not Brian Goetz and you

want to use ThreadLocals● CountDownLatch is not enough

#backdaybyxebia

Enter Specsclass MyEchoSpec(_system: ActorSystem) extends TestKit(_system) with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll {

def this() = this(ActorSystem("MySpec"))

override def afterAll { TestKit.shutdownActorSystem (system) }

"An Echo actor" must { "send back messages unchanged" in { val echo = system.actorOf(TestActors.echoActorProps) echo ! "hello world" expectMsg("hello world") } } }

#backdaybyxebia

Built-In AssertionsexpectMsg[T](d: Duration, msg: T): TexpectNoMsg(d: Duration)

expectMsgClass[T](d: Duration, c: Class[T]): TexpectMsgType[T: Manifest](d: Duration)

expectMsgAnyOf[T](d: Duration, obj: T*): TexpectMsgAnyClassOf[T](d: Duration, obj: Class[_ <: T]*): TexpectMsgAllOf[T](d: Duration, obj: T*): Seq[T]

receiveN(n: Int, d: Duration): Seq[AnyRef]

#backdaybyxebia

Multi-JVM"form a cluster" in { runOn(ClusterNodes: _*) { SeedDiscovery.joinCluster(system) enterBarrier( "deployed") }}

"support network partition" in { runOn(node1) { for { from <- group_1 to <- group_2 } { testConductor. blackhole(from, to, Direction.Both).await } }}

#backdaybyxebia

#backdaybyxebia

Use case

#backdaybyxebia

#backdaybyxebia

TCP Server DB

Actors are similar to objects.

#backdaybyxebia

Actors process one message at a time.

is injected in

usesTCP Server DB

#backdaybyxebia

TCP Server DB

session2

session1

create create

is injected in

uses

Actors can create other actors and supervise them.

#backdaybyxebia

TCP Server DB

session2

session1

create create

is injected in

uses

Actors process one message at a time.

#backdaybyxebia

TCP Server

DBrouter

session2

session1

create

is injected in

Actors can be behind routers to scale UP.

conn1

conn2

create

uses

#backdaybyxebia

TCP Server

DBrouter

session2

session1

create

is injected in

conn1

conn2

create

uses

Actors can create other actors and supervise them.

#backdaybyxebia

TCP Server

DBrouter

session2

session1

create

is injected in

conn1

conn2

create

uses

Actors can communicate with other actors.

Alertingis injected in

#backdaybyxebia

TCP Server

DBrouter

session2

session1

create

is injected in

conn1

conn2

create

uses

Actors can be deployed remotely to scale OUT.

Alertingis injected in

#backdaybyxebia

Alerting

TCP Server

DBrouter

session2

session1

create

is injected in

conn1

conn2

create

uses

Actors can be deployed remotely to scale OUT.

Alertingare injected in

#backdaybyxebia

Questions?