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
  2. AGENDA • Reactive Systems • Actors • Reactive Streams •

    Akka Streams • Example application • Q&A - Lightbend Booth
  3. THE REACTIVE MANIFESTO • Created in September 2014 • +21K

    signatures • Consists of four traits ➡ Responsive ➡ Resilient ➡ Elastic ➡ Message driven
  4. 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!
  5. 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
  6. "Akka is a toolkit for building highly concurrent, distributed, and

    resilient message-driven applications for Java and Scala." www.akka.io
  7. 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.
  8. 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
  9. 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") }
  10. 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
  11. 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?
  12. 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
  13. 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)
  14. 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
  15. 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()
  16. AKKA STREAMS IN 60s final ActorSystem system = ActorSystem.create("StreamsTest"); final

    ActorMaterialization mat = ActorMaterializer.create(system); final Source<Integer, NotUsed> source = Source.range(1, 10000); final Flow<Integer, String, NotUsed> flow = Flow.of(Integer.class).filter(n -> n % 2 == 0) .map(e -> e.toString()); final Sink<String, CompletionStage<Done>> sink = Sink.foreach(s -> System.out.println(s)); final RunnableGraph<NotUsed> runnable = source.via(flow).to(sink); runnable.run();
  17. AKKA STREAMS IN 15s Source.range(0, 10000) .filter(_ % 2 ==

    0) .map(._toString) .runForeach(n => println(s"Even number is: $n"))
  18. 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]
  19. 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], _])
  20. 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]]]
  21. 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.)
  22. 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 _ => } } }
  23. STEP 1 - ISSUES • Violates the SLA with Service

    B: ➡ Too many frequent calls. > ab -n 10 -c 10 http://localhost:8080/servicea/111
  24. 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") } } }
  25. 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
  26. 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() }
  27. STEP 3- ISSUES • Violates the SLA with Service B:

    ➡ Too many concurrent calls. > ab -n 122 -c 100 http://localhost:8080/servicea/111
  28. 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 } } } }
  29. 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…"
  30. 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) }