Designing Reactive Systems with Akka

45
DESIGNING REACTIVE SYSTEMS WITH AKKA Thomas Lockney • @tlockney http://thomas.lockney.net OSCON 2015 • Portland, Oregon

Transcript of Designing Reactive Systems with Akka

DESIGNING REACTIVE SYSTEMS WITH AKKAThomas Lockney • @tlockneyhttp://thomas.lockney.netOSCON 2015 • Portland, Oregon

WHO AM I?

Engineering at Nike+/CDT

Past experience at Cisco, Janrain, Simple, IBM/Tivoli, etc.

Founder of PNWScala, PDXScala, & various events/groups

OUR MODERN WORLD

Distributed

Mobile

Unreliable, frequent disconnects

“The Cloud”

OUR MODERN WORLD

OUR MODERN WORLD

OUR MODERN WORLD

REACTIVE SOFTWARE

Message-driven

Resilient

Elastic

Responsive

APPLYING OLD PATTERNS TO NEW CHALLENGES

INTRODUCING AKKA

Scalable concurrency toolkit

Actor model implementation (more on this shortly)

Fault tolerance

Distributed by default

THE ACTOR MODEL

Message-passing

Encapsulation of state and behavior

Mutable state is protected

Sequential internally, asynchronous between actors

A SIMPLE EXAMPLE

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

case class class log.info( }} val val name = greeter

case class Greeting(name: String)

A SIMPLE EXAMPLE

def receive = { case Greeting(name) ⇒ log.info("Hello, {}", name)}

val system = ActorSystem("MyExampleSystem") val greeter = system.actorOf(Props[GreetingActor], name = "greeter")greeter ! Greeting("Thomas")

protocol

behavior

actor system

actor creation

sending a message

case class class log.info( }} val val name = greeter

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

WHAT AKKA PROVIDES

Actor hierarchy with supervision

Flexible message routing

Configurable scheduling via dispatchers

THE HIERARCHY

THE HIERARCHY

Guardian

THE HIERARCHY

Guardian

Userhierarchy

THE HIERARCHY

Guardian

Userhierarchy

Useractors

SUPERVISION

SUPERVISION

/a/c/f fails

SUPERVISION

supervisor restarts

child

SUPERVISION

SUPERVISOR EXAMPLE

case object DieNowcase class DomainException(failureMsg: String) extends Exception(failureMsg)class Child extends Actor { import context.dispatcher scheduler.scheduleOnce(Random.nextInt(5000).milliseconds, self, DieNow) def receive = { case DieNow ⇒ throw new DomainException("R.I.P.") }}

SUPERVISOR EXAMPLE

class TopLevel extends Actor { def receive = Actor.emptyBehavior override def preStart() = { context.actorOf(Props[Child]) } override def supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 2, withinTimeRange = 10.seconds) { case DomainException(msg) ⇒ Restart }}

DESIGNING WITH AKKA

Protocols

Workflow

Failure Handling

–Merriam-Webster

“a system of rules that explain the correct conduct and procedures to be followed in formal situations.”

Protocol

PROTOCOLS

Define the interaction between actors

Specify clear boundaries of responsibility

Encode external representation of state at a point in time

Demarcate success and failure clearly

A VERY SIMPLE PROTOCOL

trait Responsecase class Success(res: Array[Byte]) extends Responsecase class Failure(err: String) extends Response

WORKFLOW

Work-pulling

Pipelines

WORK-PULLING

Enables a simple form of back-pressure

Decouples failure handling from the workflow

WORK-PULLING

EXAMPLE

case object RequestWorkcase class Work(id: String, data: String)

class Worker(queue: ActorRef) extends Actor with ActorLogging { override def preStart = { queue ! RequestWork } def receive = { case w:Work ⇒ log.info(w.data.toUpperCase) queue ! RequestWork } }

EXAMPLEclass Queuer extends Actor { val queuedWork = Queue.empty[Work] val requestors = Queue.empty[ActorRef]

def receive = { case RequestWork if (queuedWork.nonEmpty) ⇒ sender ! queuedWork.dequeue case RequestWork ⇒ requestors.enqueue(sender) case w:Work if (requestors.nonEmpty) ⇒ requestors.dequeue ! w case w:Work ⇒ queuedWork.enqueue(w) } }

PIPELINES

Commonly found in data processing applications

Distinct phases of data responsibility

May span one or more systems

Often have radically varying scaling needs

PIPELINES

Each phase has its own failure domain

Back-pressure is managed through the chain

The simplified view

PIPELINES

Each phase has a failure domain

Back-pressure is still managed through the chain

The more typical view

PIPELINES

Often just an extension of more basic patterns

Work-pulling is a common component

Keep an eye on Akka Streams!

FAULT-TOLERANCE

Circuit-breakers

Isolate key systems

Fault isolation and failure-domains

Restrict the size of the impact

CIRCUIT-BREAKER

Isolate important components

Avoid cascading failures

CIRCUIT-BREAKER

class FailingActor extends Actor { import context.dispatcher system.scheduler.scheduleOnce(Random.nextInt(5000).milliseconds, self, Die) def receive = { case Die ⇒ throw new Exception("I've fallen and I can't get up!") case s: String ⇒ sender ! s.toUpperCase }}

CIRCUIT-BREAKER

val breaker = new CircuitBreaker(context.system.scheduler, maxFailures = 5, callTimeout = 10.seconds, resetTimeout = 1.minute)def receive = { case Tick ⇒ val in = Random.alphanumeric.take(10).mkString breaker.withCircuitBreaker(toUpper ? in) pipeTo self case s:String ⇒ log.info("Received: {}", s)}

FAILURE-DOMAINS AND FAULT ISOLATION

FAILURE-DOMAINS AND FAULT ISOLATION

These are failure-domains

FAILURE-DOMAINS AND FAULT ISOLATION

FAILURE-DOMAINS AND FAULT ISOLATION

These don’t have to care

THANK YOU! QUESTIONS?