Designing Reactive Systems with Akka
-
Upload
thomas-lockney -
Category
Software
-
view
1.353 -
download
2
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
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
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 }}
–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
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
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)}