Building Reactive Applications with Akka (in Scala)

Post on 19-Aug-2014

6.108 views 11 download

Tags:

description

Abstract: The demands and expectations for applications have changed dramatically in recent years. Applications today are deployed on a wide range of infrastructure; from mobile devices up to thousands of nodes running in the cloud—all powered by multi-core processors. They need to be rich and collaborative, have a real-time feel with millisecond response time and should never stop running. Additionally, modern applications are a mashup of external services that need to be consumed and composed to provide the features at hand. We are seeing a new type of applications emerging to address these new challenges—these are being called Reactive Applications. In this talk we will introduce you to Akka and discuss how it can help you deliver on the four key traits of Reactive; Event-Driven, Scalable, Resilient and Responsive. We will start with the basics of Akka and work our way towards some of its more advanced modules such as Akka Cluster and Akka Persistence—all driven through code and practical examples.

Transcript of Building Reactive Applications with Akka (in Scala)

Building Reactive Applications with Akka

Jonas Bonér Typesafe

CTO & co-founder @jboner

This is an era of profound change.

Reactive  Applications

Implications are massive, change is unavoidable

3

!!!Users are demanding richer and more personalized experiences. !Yet, at the same time, expecting

blazing fast load time.

Users!!!

Mobile and HTML5; Data and compute clouds; scaling on

demand. !Modern application technologies

are fueling the always-on, real-time user expectation.

Applications !!!Businesses are being pushed to

react to these changing user expectations… !...and embrace

modern application requirements. !!!!!!!

Businesses

As a matter of necessity, businesses are going Reactive.

Reactive  Applications

Reactive applications share four traits

5

Reactive applications react to changes in the world around them.

Event-Driven• Loosely coupled architecture, easier to extend, maintain, evolve

• Asynchronous and non-blocking • Concurrent by design, immutable state • Lower latency and higher throughput

7

“Clearly, the goal is to do these operations concurrently and non-blocking, so that entire blocks of seats or sections are not locked.

We’re able to find and allocate seats under load in less than 20ms without trying very hard to achieve it.”  

Andrew Headrick, Platform Architect, Ticketfly

Introducing the Actor Model.

9

The Actor Model

9

A computational model that embodies:

The Actor Model

9

A computational model that embodies:

✓ Processing

The Actor Model

9

A computational model that embodies:

✓ Processing

✓ Storage

The Actor Model

9

A computational model that embodies:

✓ Processing

✓ Storage

✓ Communication

The Actor Model

9

A computational model that embodies:

✓ Processing

✓ Storage

✓ Communication

Supports 3 axioms—when an Actor receives a message it can:

The Actor Model

9

A computational model that embodies:

✓ Processing

✓ Storage

✓ Communication

Supports 3 axioms—when an Actor receives a message it can:

1. Create new Actors

The Actor Model

9

A computational model that embodies:

✓ Processing

✓ Storage

✓ Communication

Supports 3 axioms—when an Actor receives a message it can:

1. Create new Actors

2. Send messages to Actors it knows

The Actor Model

9

A computational model that embodies:

✓ Processing

✓ Storage

✓ Communication

Supports 3 axioms—when an Actor receives a message it can:

1. Create new Actors

2. Send messages to Actors it knows

3. Designate how it should handle the next message it receives

The Actor Model

The essence of an actor0. DEFINE

1. CREATE

2. SEND

3. BECOME

4. SUPERVISE

10

0. DEFINE

11

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info(s"Hello ${who}") } }

0. DEFINE

11

Define the message(s) the Actor should be able to respond to

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info(s"Hello ${who}") } }

0. DEFINE

11

Define the message(s) the Actor should be able to respond to

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info(s"Hello ${who}") } }

Define the Actor class

0. DEFINE

11

Define the message(s) the Actor should be able to respond to

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info(s"Hello ${who}") } }

Define the Actor class

Define the Actor’s behavior

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info("Hello " + who) } } !val system = ActorSystem("MySystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter")

1. CREATE

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info("Hello " + who) } } !val system = ActorSystem("MySystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter")

1. CREATE

Create an Actor system

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info("Hello " + who) } } !val system = ActorSystem("MySystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter")

1. CREATE

Create an Actor systemActor configuration

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info("Hello " + who) } } !val system = ActorSystem("MySystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter")

Give it a name

1. CREATE

Create an Actor systemActor configuration

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info("Hello " + who) } } !val system = ActorSystem("MySystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter")

Give it a name

1. CREATE

Create the Actor

Create an Actor systemActor configuration

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info("Hello " + who) } } !val system = ActorSystem("MySystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter")

Give it a name

1. CREATE

Create the ActorYou get an ActorRef back

Create an Actor systemActor configuration

Guardian System Actor

Actors can form hierarchies

Guardian System Actor

system.actorOf(Props[Foo], “Foo”)

Actors can form hierarchies

Foo

Guardian System Actor

system.actorOf(Props[Foo], “Foo”)

Actors can form hierarchies

Foo

Guardian System Actor

context.actorOf(Props[A], “A”)

Actors can form hierarchies

A

Foo

Guardian System Actor

context.actorOf(Props[A], “A”)

Actors can form hierarchies

A

B

BarFoo

C

BE

A

D

C

Guardian System Actor

Actors can form hierarchies

A

B

BarFoo

C

BE

A

D

C

Guardian System Actor

Name resolution—like a file-system

A

B

BarFoo

C

BE

A

D

C

/Foo

Guardian System Actor

Name resolution—like a file-system

A

B

BarFoo

C

BE

A

D

C

/Foo

/Foo/A

Guardian System Actor

Name resolution—like a file-system

A

B

BarFoo

C

BE

A

D

C

/Foo

/Foo/A

/Foo/A/B

Guardian System Actor

Name resolution—like a file-system

A

B

BarFoo

C

BE

A

D

C

/Foo

/Foo/A

/Foo/A/B

/Foo/A/D

Guardian System Actor

Name resolution—like a file-system

2. SEND

15

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info(s”Hello ${who}") } } !val system = ActorSystem("MySystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter") greeter ! Greeting("Charlie Parker")

2. SEND

15

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info(s”Hello ${who}") } } !val system = ActorSystem("MySystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter") greeter ! Greeting("Charlie Parker")

Send the message asynchronously

Bring it together

16

case class Greeting(who: String) !class GreetingActor extends Actor with ActorLogging { def receive = { case Greeting(who) => log.info(s”Hello ${who}") } } !val system = ActorSystem("MySystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter") greeter ! Greeting("Charlie Parker")

DEMO TIMEA simple game of ping pong

3. BECOME

18

class GreetingActor extends Actor with ActorLogging { def receive = happy ! val happy: Receive = { case Greeting(who) => log.info(s”Hello ${who}") case Angry => context become angry } ! val angry: Receive = { case Greeting(_) => log.info("Go away!") case Happy => context become happy } }

3. BECOME

18

class GreetingActor extends Actor with ActorLogging { def receive = happy ! val happy: Receive = { case Greeting(who) => log.info(s”Hello ${who}") case Angry => context become angry } ! val angry: Receive = { case Greeting(_) => log.info("Go away!") case Happy => context become happy } }

Redefine the behavior

Reactive applications are architected to handle failure at all levels.

Resilient• Failure is embraced as a natural state in the app lifecycle

• Resilience is a first-class construct • Failure is detected, isolated, and managed • Applications self heal

20

“The Typesafe Reactive Platform helps us maintain a very aggressive development and deployment cycle, all in a fail-forward manner.

It’s now the default choice for developing all new services.”  

Peter Hausel, VP Engineering, Gawker Media

Think Vending Machine

Coffee MachineProgrammer

Think Vending Machine

Coffee MachineProgrammer

Inserts coins

Think Vending Machine

Coffee MachineProgrammer

Inserts coins

Add more coins

Think Vending Machine

Coffee MachineProgrammer

Inserts coins

Gets coffee

Add more coins

Think Vending Machine

Coffee MachineProgrammer

Think Vending Machine

Coffee MachineProgrammer

Inserts coins

Think Vending Machine

Coffee MachineProgrammer

Inserts coins

Think Vending Machine

Out of coffee beans error

Coffee MachineProgrammer

Inserts coins

Think Vending Machine

Out of coffee beans errorWrong

Coffee MachineProgrammer

Inserts coins

Think Vending Machine

Coffee MachineProgrammer

Inserts coins

Out of coffee beans

error

Think Vending Machine

Coffee MachineProgrammer

Service Guy

Inserts coins

Out of coffee beans

error

Think Vending Machine

Coffee MachineProgrammer

Service Guy

Inserts coins

Out of coffee beans

error

Adds more beans

Think Vending Machine

Coffee MachineProgrammer

Service Guy

Inserts coins

Gets coffee

Out of coffee beans

error

Adds more beans

Think Vending Machine

The Right Way

ServiceClient

The Right Way

ServiceClient

Request

The Right Way

ServiceClient

Request

Response

The Right Way

ServiceClient

Request

Response

Validation Error

The Right Way

ServiceClient

Request

Response

Validation Error

Application Error

The Right Way

ServiceClient

Supervisor

Request

Response

Validation Error

Application Error

The Right Way

ServiceClient

Supervisor

Request

Response

Validation Error

Application Error

Manages Failure

• Isolate the failure

• Compartmentalize

• Manage failure locally

• Avoid cascading failures

Use Bulkheads

• Isolate the failure

• Compartmentalize

• Manage failure locally

• Avoid cascading failures

Use Bulkheads

Enter Supervision

Enter Supervision

A

B

BarFoo

C

BE

A

D

C

Automatic and mandatory supervisionSupervisor hierarchies

4. SUPERVISE

28

Every single actor has a default supervisor strategy.

Which is usually sufficient. But it can be overridden.

4. SUPERVISE

28

Every single actor has a default supervisor strategy.

Which is usually sufficient. But it can be overridden.

class Supervisor extends Actor { override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { case _: ArithmeticException => Resume case _: NullPointerException => Restart case _: Exception => Escalate } ! val worker = context.actorOf(Props[Worker], name = "worker") ! def receive = {

4. SUPERVISE

28

class Supervisor extends Actor { override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { case _: ArithmeticException => Resume case _: NullPointerException => Restart case _: Exception => Escalate } ! val worker = context.actorOf(Props[Worker], name = "worker") ! def receive = { case n: Int => worker forward n } } !

Cleanup & (Re)initialization

29

class Worker extends Actor { ... override def preRestart( reason: Throwable, message: Option[Any]) { ... // clean up before restart } override def postRestart(reason: Throwable) { ... // init after restart } }

Monitor through Death Watch

30

class Watcher extends Actor { val child = context.actorOf(Props.empty, "child") context.watch(child) ! def receive = { case Terminated(`child`) => … // handle child termination } }

Reactive applications scale up and down to meet demand.

Scalable• Scalability and elasticity to embrace the Cloud

• Leverage all cores via asynchronous programming • Clustered servers support joining and leaving of nodes • More cost-efficient utilization of hardware

32

“Our traffic can increase by as much as 100x for 15 minutes each day. Until a couple of years ago, noon was a stressful time.

Nowadays, it’s usually a non-event.”  

Eric Bowman, VP Architecture, Gilt Groupe

33

Scale OUTScale UP

33

Essentially the same thing

34

1. Minimize Contention 2. Maximize Locality of Reference

We need to

35

Share NOTHING

Design

Fully event-driven apps are a necessity

36

Amdahl’s Law will hunt you down

Define a router

37

val router = context.actorOf( RoundRobinPool(5).props(Props[Worker])), “router”)

Paths can be local or remote actor paths

…or from config

38

akka.actor.deployment { /service/router { router = round-robin-pool resizer { lower-bound = 12 upper-bound = 15 } } }

Turn on clustering

39

akka { actor { provider = "akka.cluster.ClusterActorRefProvider" ... }    cluster { seed-nodes = [ “akka.tcp://ClusterSystem@127.0.0.1:2551", “akka.tcp://ClusterSystem@127.0.0.1:2552" ]   auto-down = off } }

Use clustered routers

40

akka.actor.deployment  {      /service/master  {          router  =  consistent-­‐hashing-­‐pool          nr-­‐of-­‐instances  =  100  !        cluster  {              enabled  =  on              max-nr-of-instances-per-node = 3              allow-­‐local-­‐routees  =  on              use-­‐role  =  compute          }      }  }

Use clustered routers

40

akka.actor.deployment  {      /service/master  {          router  =  consistent-­‐hashing-­‐pool          nr-­‐of-­‐instances  =  100  !        cluster  {              enabled  =  on              max-nr-of-instances-per-node = 3              allow-­‐local-­‐routees  =  on              use-­‐role  =  compute          }      }  }

Or perhaps use an AdaptiveLoadBalancingPool

Use clustered pub-sub

41

Use clustered pub-sub

41

class Subscriber extends Actor { val mediator = DistributedPubSubExtension(context.system).mediator mediator ! Subscribe(“content”, self) def receive = { … } }

Use clustered pub-sub

41

class Publisher extends Actor { val mediator = DistributedPubSubExtension(context.system).mediator def receive = { case in: String => mediator ! Publish("content", in.toUpperCase) } }

• Cluster Membership • Cluster Leader • Clustered Singleton • Cluster Roles • Cluster Sharding

42

Other Akka Cluster features

• Supports two different models: • Command Sourcing — at least once

• Event Sourcing — at most once

• Great for implementing • durable actors • replication • CQRS etc.

• Messages persisted to Journal and replayed on restart

43

Use Akka Persistence

Akka  Persistence  Webinar

Command Sourcing Event Sourcingwrite-ahead-log derive events from a command

same behavior during recovery as normal operation external interaction can be problematic

only state-changing behavior during recovery

persisted before validation events cannot fail

allows retroactive changes to the business logic

fixing the business logic will not affect persisted events

naming: represent intent, imperative naming: things that have completed, verbs in past tense

Akka  Persistence  Webinar

Life beyond Distributed Transactions: an Apostate’s Opinion

Position Paper by Pat Helland

“In general, application developers simply do not implement large scalable applications assuming distributed transactions.”  

Pat Helland

http://www-­‐db.cs.wisc.edu/cidr/cidr2007/papers/cidr07p15.pdf

Akka  Persistence  Webinar

Consistency boundary

• Aggregate Root is the Transactional Boundary • Strong consistency within an Aggregate • Eventual consistency between Aggregates

• No limit to scalability

Akka  Persistence  Webinar

Domain Events• Things that have completed, facts

• Immutable

• Verbs in past tense • CustomerRelocated • CargoShipped • InvoiceSent

“State transitions are an important part of our problem space and should be modeled within our domain.”  

Greg Young, 2008

DEMO TIMEPersist a game of ping pong

Reactive applications enrich the user experience with low latency response.

Responsive• Real-time, engaging, rich and collaborative

• Create an open and ongoing dialog with users • More efficient workflow; inspires a feeling of connectedness • Fully Reactive enabling push instead of pull

50

“The move to these technologies is already paying off. Response times are down for processor intensive code–such as image

and PDF generation–by around 75%.”  

Brian Pugh, VP of Engineering, Lucid Software

Responsive

51

• Keep latency consistent—under:

1. Blue sky scenarios

2. Traffic spikes

3. Failures

• The system should always be responsive

http://reactivemanifesto.org

Typesafe Activatorhttp://typesafe.com/platform/getstarted

54

Typesafe Reactive Platform

54

Typesafe Reactive Platform

• Asynchronous and immutable programming constructs

• Composable abstractions enabling simpler concurrency and parallelism

54

Typesafe Reactive Platform

• Actors are asynchronous and communicate via message passing

• Supervision and clustering in support of fault tolerance

• Asynchronous and immutable programming constructs

• Composable abstractions enabling simpler concurrency and parallelism

54

Typesafe Reactive Platform

• Actors are asynchronous and communicate via message passing

• Supervision and clustering in support of fault tolerance

• Purely asynchronous and non-blocking web frameworks

• No container required, no inherent bottlenecks in session management

• Asynchronous and immutable programming constructs

• Composable abstractions enabling simpler concurrency and parallelism

Reactive is being adopted acrossa wide range of industries.

56

Finance Internet/Social Media Mfg/Hardware Government Retail

Questions?

©Typesafe 2014 – All Rights Reserved