Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DevNexus ATL 2018

h3nk3
February 22, 2018

DevNexus ATL 2018

Akka Streams presentation that was given at DevNexus in Atlanta, GA, 2018.

h3nk3

February 22, 2018
Tweet

More Decks by h3nk3

Other Decks in Programming

Transcript

  1. Akka Streams: a match in heaven for
    Reactive Systems
    Henrik Engström
    Senior Software Engineer, Lightbend
    DevNexus, Atlanta - February 2018
    Slides available here:
    http://speakerdeck.com/h3nk3/devnexus-atl-2018

    View full-size slide

  2. AGENDA
    • Reactive Systems
    • Actors
    • Reactive Streams
    • Akka Streams
    • Example application
    • Q&A - Lightbend Booth

    View full-size slide

  3. REACTIVE SYSTEMS

    View full-size slide

  4. THE REACTIVE MANIFESTO
    • Created in September 2014
    • +21K signatures
    • Consists of four traits
    ➡ Responsive
    ➡ Resilient
    ➡ Elastic
    ➡ Message driven

    View full-size slide

  5. MESSAGE DRIVEN

    View full-size slide

  6. ACTORS: A PROVEN MODEL
    • Successfully used in the Telecom industry since
    the 80s.
    • Extremely reliable:
    ➡ 9 nines uptime, or…
    ➡ 99.9999999 percent uptime, or…
    ➡ 32ms downtime per year!

    View full-size slide

  7. THE ACTOR MODEL IS NOT NEW
    • Carl Hewitt’s definition is from 1973
    ➡ Process, Store, and Communicate
    • An actor should follow three axioms:
    ➡ Create new actors
    ➡ Send messages to actors
    ➡ Designate how to handle the next message

    View full-size slide

  8. "Akka is a toolkit for building highly concurrent,
    distributed, and resilient message-driven applications
    for Java and Scala."
    www.akka.io

    View full-size slide

  9. THE AKKA TOOLKIT
    • Actors: simple and high performance concurrency
    • Cluster/Remoting: elasticity, resilience
    • Persistence: event sourcing, resilience
    • Streams: back-pressured stream processing
    • HTTP: reactive HTTP server and client
    • and more!
    Complete APIs in both Java and Scala.

    View full-size slide

  10. SOME AKKA USERS

    View full-size slide

  11. Some AKKA highlights
    • Everything in Akka is asynchronous
    • Akka actors give the illusion of a single
    threaded programming model
    • The programming model stays the same
    regardless of target environment
    • Akka actors are location transparent

    View full-size slide

  12. AKKA ACTOR ANATOMY

    View full-size slide

  13. ACTORSYSTEM ANATOMY

    View full-size slide

  14. AKKA ACTOR EXAMPLE
    case class Deposit(sum: Long)
    case class Withdraw(sum: Long)
    class AccountActor extends Actor with ActorLogging {
    var accountSum = 0L // example of state in an actor
    def receive = {
    case Deposit(sum) =>
    accountSum += sum
    logStatus
    case Withdraw(sum) =>
    if (sum <= accountSum) accountSum -= sum
    logStatus
    }
    def logStatus = log.info(s"Account sum is: $accountSum")
    }

    View full-size slide

  15. AKKA ACTOR EXAMPLE
    val system: ActorSystem = ActorSystem("ActorTest")
    val account1: ActorRef =
    system.actorOf(Props[AccountActor], "account1")
    account1 ! Deposit(100L)
    account1 ! Withdraw(200L)
    account1 ! Withdraw(99L)
    LOG OUTPUT WHEN RUNNING THE SYSTEM:
    Account sum is 100
    Account sum is 100
    Account sum is 1

    View full-size slide

  16. ACTORS - ALL YOU NEED TO BE REACTIVE?
    •Actors are awesome, but there are scenarios
    that are better handled by Streams.
    •Example: There is no backpressure with Akka
    Actors and all internal queues are unbounded
    by default.
    •Have you ever tried drinking from a fire hose?

    View full-size slide

  17. REACTIVE STREAMS

    View full-size slide

  18. REACTIVE STREAMS
    "Reactive Streams is an initiative to provide a standard for
    asynchronous stream processing with non-blocking back
    pressure. This encompasses efforts aimed at runtime
    environments (JVM and JavaScript) as well as network
    protocols."
    www.reactive-streams.org

    View full-size slide

  19. STREAM DEFINITION
    • A possibly infinite set of elements
    • Processed element by element
    • Processed asynchronously:
    ➡ Sender and receiver are decoupled
    ➡ Asynchronous boundaries (threads)
    ➡ Network boundaries (nodes)

    View full-size slide

  20. REACTIVE STREAMS VISUALIZED

    View full-size slide

  21. ALTERNATIVE VISUALIZATION

    View full-size slide

  22. ASYNCHRONOUS PROCESSING

    View full-size slide

  23. FAST SOURCE SCENARIO

    View full-size slide

  24. PROCESSING: OUT OF MEMORY

    View full-size slide

  25. PROCESSING: BOUNDED QUEUES

    View full-size slide

  26. ASYNCHRONOUS & BACK-PRESSURE

    View full-size slide

  27. REACTIVE STREAMS SPECIFICATION

    View full-size slide

  28. SOME STREAMS IMPLEMENTATIONS

    View full-size slide

  29. REACTIVE STREAMS IN JAVA 9
    • java.util.concurrent.Flow.Publisher
    ➡ Producer of items
    • java.util.concurrent.Flow.Subscriber
    ➡ Receiver of messages
    • java.util.concurrent.Flow.Subscription
    ➡ Message control linking a Publisher and Subscriber
    • java.util.concurrent.Flow.Processor
    ➡ Component that acts like both Publisher and Subscriber

    View full-size slide

  30. AKKA STREAMS

    View full-size slide

  31. AKKA STREAMS IN 60s
    implicit val system = ActorSystem("StreamsTest")
    implicit val materializer = ActorMaterializer()
    val source = Source(1 to 10000)
    val flow = Flow[Int].filter(_ % 2 == 0).map(_.toString())
    val sink = Sink.foreach[String](n => s"Even number is: $n")
    val runnable = source.via(flow).to(sink)
    runnable.run()

    View full-size slide

  32. AKKA STREAMS IN 60s
    final ActorSystem system = ActorSystem.create("StreamsTest");
    final ActorMaterialization mat =
    ActorMaterializer.create(system);
    final Source source = Source.range(1, 10000);
    final Flow flow =
    Flow.of(Integer.class).filter(n -> n % 2 == 0)
    .map(e -> e.toString());
    final Sink> sink =
    Sink.foreach(s -> System.out.println(s));
    final RunnableGraph runnable =
    source.via(flow).to(sink);
    runnable.run();

    View full-size slide

  33. AKKA STREAMS IN 15s
    Source.range(0, 10000)
    .filter(_ % 2 == 0)
    .map(._toString)
    .runForeach(n => println(s"Even number is: $n"))

    View full-size slide

  34. akka.stream.scaladsl.Source
    def fromIterator[T](f: () => Iterator[T]): Source[T, NotUsed]
    def fromFuture[T](future: Future[T]): Source[T, NotUsed]
    def tick[T](initialDelay: FiniteDuration, interval:
    FiniteDuration, tick: T):
    Source[T, Cancellable]
    def single[T](element: T): Source[T, NotUsed]
    def repeat[T](element: T): Source[T, NotUsed]
    def empty[T]: Source[T, NotUsed]

    View full-size slide

  35. akka.stream.scaladsl.Flow
    def drop(n: Long)
    def filter(p: Out => Boolean)
    def groupedWithin(n: Int, d: FiniteDuration)
    def initialDelay(delay: FiniteDuration)
    def map[T](f: Out => T)
    def mapAsync[T](parallelism: Int)(f: Out => Future[T])
    def takeWhile(p: Out => Boolean)
    def zip[U](that: Graph[SourceShape[U], _])

    View full-size slide

  36. akka.stream.scaladsl.Sink
    def foreach[T](f: T => Unit): Sink[T, Future[Done]]
    def ignore: Sink[Any, Future[Done]]
    def head[T]: Sink[T, Future[T]]
    def actorRef[T](ref: ActorRef, onCompleteMessage: Any):
    Sink[T, NotUsed]
    def last[T]: Sink[T, Future[T]]
    def seq[T]: Sink[T, Future[immutable.Seq[T]]]

    View full-size slide

  37. EXAMPLE APPLICATION

    View full-size slide

  38. MICROSERVICE APPLICATION
    • Two services:
    ➡ Service A: the one we are implementing.
    ➡ Service B: existing service that we re-use.
    • Service B has a quite tough SLA to meet:
    ➡ Not too many simultaneous calls (max 5.)
    ➡ Not too many frequent calls (max 1/s.)

    View full-size slide

  39. ARCHITECTURE DIAGRAM

    View full-size slide

  40. STEP 1
    class Try1Actor extends BaseActor {
    def receive: Receive = {
    case id: Int =>
    val s = sender()
    httpRequest(id.toString).onComplete {
    case Success(result) => s ! result
    case _ =>
    }
    }
    }

    View full-size slide

  41. STEP 1 - ISSUES
    • Violates the SLA with Service B:
    ➡ Too many frequent calls.
    > ab -n 10 -c 10 http://localhost:8080/servicea/111

    View full-size slide

  42. STEP 2 - BATCH FTW!
    var ids = Map.empty[ActorRef, Int]
    def receive = { case id: Int => batch(sender(), id) }
    def batch(sender: ActorRef, id: Int) = {
    ids += sender -> id
    if (ids.size == batchSize) {
    val copy = ids
    ids = Map.empty[ActorRef, Int]
    httpRequest(copy.values.mkString("-")).onComplete {
    case Success(result) =>
    copy.foreach {
    case (actorRef, id) => actorRef ! result
    }
    case Failure(f) => log.error(s"Coll to service failed: $f")
    }
    }
    }

    View full-size slide

  43. STEP 2 - ISSUES
    • Calls time out if not even with the set limit!
    ➡ E.g. limit = 10.
    > ab -n 12 -c 12 http://localhost:8080/servicea/111

    View full-size slide

  44. STEP 3 - SCHEDULER
    val checkStatusScheduler =
    context.system.scheduler.schedule
    (batchFrequency, batchFrequency, self, CheckBatchStatus)
    override def postStop(): Unit = {
    checkStatusScheduler.cancel()
    }
    def receive = {
    case id: Int => batch(sender(), id)
    case CheckBatchStatus =>
    if(requestDone) requestDone = false
    else makeRequest()
    }

    View full-size slide

  45. STEP 3- ISSUES
    • Violates the SLA with Service B:
    ➡ Too many concurrent calls.
    > ab -n 122 -c 100 http://localhost:8080/servicea/111

    View full-size slide

  46. STEP 4- LIMIT CONCURRENT CALLS
    def makeRequest() = {
    if (concurrentCalls <= maxConcurrentCalls)
    if (!ids.isEmpty) {
    concurrentCalls += 1
    val split = ids.splitAt(batchSize)
    val copy = split._1
    ids = split._2
    httpRequest(copy.values.mkString("-")).onComplete {
    case Success(result) =>
    copy.foreach {
    case (actorRef, id) => actorRef ! result
    }
    concurrentCalls -= 1
    case Failure(f) => concurrentCalls -= 1
    }
    }
    }
    }

    View full-size slide

  47. STEP 4- PROBLEM SOLVED!!
    When we describe our solution to that annoying,
    and astute(!), colleague we all have…
    - "This sounds all good, but have you heard about
    Akka Streams and all it’s awesome features? You
    should definitely give it a try and see if it can help
    simplify the code base a little?"
    - "Okay, if you say so…"

    View full-size slide

  48. STEP 5 - AKKA STREAMS
    case class Request(id: Int, responsePromise: Promise[HttpResponse])
    val sink =
    MergeHub.source[Request]
    .groupedWithin(batchSize, batchFrequency)
    .map(requests =>
    (requests.map(req => req.id).mkString("-"),
    requests.map(_.responsePromise)))
    .mapAsyncUnordered(maxConcurrentCalls)(payload =>
    httpRequest(payload._1, payload._2))
    .to(Sink.ignore)
    .run()
    def receive = {
    case req: Request => Source.single(req).runWith(sink)
    }

    View full-size slide

  49. http://github.com/henrikengstrom/devnexus2018
    EXAMPLE SOURCE CODE

    View full-size slide