Agenda
- Why go Reactive?
- The Reactive Manifesto
- Streams
- Reactive Streams
- Implementations
- Books
- Thanks
Our old tools don't cut it
- Threads (programmed directly)
- high memory overhead
- starting/stopping is expensive
- inter-thread communication entirely left to the user
- Locks/Mutexes/Semaphores/`synchronized`/`volatile`
- too little sync: race conditions, wrong results
- too much sync: deadlocks, poor performance
- very hard to use correctly
Too low-level!
Selected concurrency abstractions
Responsive
- responds in a timemly manner if at all possible
- focus on providing rapid and consistent response times
- establishing reliable upper bounds
- deliver a consistent quality of service.
- simplifies error handling,
Resilient
- Let it crash
- stays responsive in the face of failure
- achieved by replication, containment, isolation and delegation
- failures are contained within each component
- recovery of each component is delegated to another (external) component
Elastic
- stays responsive under varying workload
- scale resources based on input rate
- implies designs that have no contention points or central bottlenecks
Message Driven
- asynchronous message-passing to establish a boundary between components
- ensures loose coupling, isolation, location transparency
- delegate errors as messages
- enables load management, elasticity, and flow control
- shaping and monitoring the message queues in the system and applying back-pressure
Kilde: http://www.cgenarchive.org/uploads/2/5/2/6/25269392/7797686_orig.jpg
Common Uses of Streams
- Bulk data transfer
- Real-time data sources
- Batch processing of large data sets
- Monitoring and analytics
- Structuring and Scaling Business Logic
A stream is not an colleciton
- size
- x.head + x.tail
- iterate all elements
- content not dependent on who is processing
- content not dependent on when it is processed
- speed of the processor
- speed of the producer
Simple stream example
import java.util.stream.*
final Stream<Integer> s = Stream.of(1,2,3);
final Stream<String> s2 = s.map(i -> "a" + 1);
s2.iterator(); //pull
s2.forEach(i -> System.out.println(i)); //push
Java8 Stream
- dsl for describing transformation
- staged computation
- cannot be reused
- eager model of execution
- push/pull chosen statically
statically-typed, high-performance, low latency, asynchronous streams of data with built-in non-blocking back pressure
Success formula
- minimial interface
- rigorous specification of semantics
- TCK for verification
- API freedom
Interface
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
public interface Subscription {
public void request(long n);
public void cancel();
}
public interface Processor<T, R>
extends Subscriber<T>, Publisher<R> {
}
Not for user consumption!
(Use RS Impl Library instead)
sequenceDiagram
participant Publisher
participant Subscriber
Subscriber->>Publisher: subscribe(Subscription)
Publisher->>Subscriber: onSubscribe(Subscription)
Subscriber->>Publisher: Subscription.request(3)
loop 3 times
Publisher->>Subscriber: onNext
end
Publisher->>Subscriber: onComplete | onError
Async Boundary
- Data elements flow downstream
- Demand flows upstream
- Data items flow only when there is demand
- Recipient is in control of incoming data rate
- Data in flight is bounded by signaled demand
Kilde:http://media-cdn.tripadvisor.com/media/photo-s/05/be/70/1a/river-split.jpg
Kilde: https://hippiefreak69.files.wordpress.com/2015/07/river.jpg
DYNAMIC PUSH/PULL
- “Push” behavior when consumer is faster
- “Pull” behavior when producer is faster
- Switches automatically between these
- Batching demand allows batching data
ASYNC BOUNDARIES ARE EVERYWHERE
- Between actors
- Between threads
- Between CPUs
- Between network nodes
- Between applications
Pipeline Processing Done Right
Continuous Pipelines across Machines
AKKA-STREAM: BASIC CONCEPTS
- Built from scratch on Reactive Streams Protocol
- Java and Scala user APIs
- Supports fully general stream-graphs
- Source[T]: the open end of a pipeline producing Ts
- Sink[T]: an "end-piece" for taking in Ts
- Flow[A, B]: an unconnected piece of pipeline
- Generally, all abstractions are re-useable
- Number of pre-defined transformations still somewhat small
- Not very optimized yet, still marked "experimental"
AKKA-STREAM: SIMPLE STREAM EXAMPLE
Source(stockTickerPublisher) // Source[Tick]
.filter(_.symbol == "AAPL") // Source[Tick]
.buffer(100000, OverflowStrategy.DropHead) // Source[Tick]
.splitWhen(x => isNewDay(x.timeStamp)) // Source[Source[Tick]]
.headAndTail // Source[(Tick, Source[Tick])]
.map { case (head, tail) =>
head -> tail.groupedWithin(1000, 1.second)
} // Source[(Tick, Source[Seq[[Tick]]])]
.via(someFlow) // Source[RichTick]
.map(toCandleStickChartColumn) // Source[CandleStickChartColumn]
.to(candleStickChartSink) // RunnableFlow
.run() // MaterializedMap
AKKA-STREAM: FLOW GRAPH EXAMPLE
FlowGraph { implicit b ⇒
val bcast = Broadcast[T]
val merge = Merge[T]
source ~> f1 ~> bcast ~> f2 ~> merge ~> f3 ~> sink
bcast ~> f4 ~> merge
}.run()
REACTIVE EXTENSIONS
- Mostly driven by Netflix Engineers
- Broke the ground for Reactive Streams
- Oldest, most known, most mature
- Support for many different languages
- Doesn't directly support RS yet,
- currently still needs adapter lib
- Backpressure not fully supported yet,
- will do so in version 2.0
REACTOR
- RS implementation built from scratch
- Java only
- Internally based a lot on Disruptor
- Focus: Performance
- Current version (2.0.3) not very mature yet
- Driven by Engineers from Pivotal
Everything you never knew you wanted to know about
Reactive Streams
Bjarte Stien Karlsen @ SITS
slides created with revealit/reveal.js