reactive streams. slava schmidt
TRANSCRIPT
Reactive
Streams
THENJava DeveloperLATEREnterprise ArchitectNOWScala Consultant
TWITTER@[email protected]
Reactive
Streams
Stream
A stream can be defined as a sequence of
data.
A powerful concept that greatly simplifies I/
O operations.
Java 5/6/7
A sequence of elements
supporting sequential and
parallel aggregate operations.
Java 8
•sequence of data or instructions•hot or cold•bounded or unbounded•focus on data transfer and/or transformation
Uses
•bulk data transfer•batch processing of large data sets•micro-batching•real-time data sources•embedded data-processing•monitoring and analytics•metrics, statistics composition •event processing•error handling
MEETALICE AND BORIS
MEETALICE AND BORIS
Java 6
Java 7
Java 8
NAK (NACK)
Backpressure
Reactive Stream
MEETALICE AND BORIS
•Typical threads
•Do some work
•Exchange data
Direct Callsprivate static final SThread alice = new SThread("RS-Alice") { @Override public void swap(int count) { super.swap(count); borice.swap(count); } };
private static final SThread borice = new SThread("RS-Borice") { @Override public void run() { while (!stopped()) { SwapEnvironment.work(); } } @Override public void swap(int count) { super.swap(count); } };
Java IO
“Let’s move some data”
Java IO
A PipedOutputStream
PipedInputStreamprotected byte buffer[];
B
Java IOA PipedOutputStream BPipedInputStream
protected byte buffer[];
Java IOA PipedOutputStream BPipedInputStream
Write
Block
Transfer
Block
Read
Block
Java IOval alice = new SThread("RS-Alice") { override def swap(n: Int) { super.swap(n) for (i <- 0 to n * rateA) out.write(Env.BEER) }}
val borice = new SThread("RS-Borice") { override def swap(n: Int) { super.swap(n) for (i <- 0 to n * rateB) in.read() }}
val out = new PipedOutputStream()val in = new PipedInputStream(out, size)
Java IOval alice = new SThread("RS-Alice") { override def swap(n: Int) { super.swap(n) for (i <- 0 to n * rateA) out.write(Env.BEER) }}
val borice = new SThread("RS-Borice") { override def swap(n: Int) { super.swap(n) for (i <- 0 to n * rateB) in.read() }}
val out = new PipedOutputStream()val in = new PipedInputStream(out, size)
INCREASE
Java IOval alice = new SThread("RS-Alice") { override def swap(n: Int) { super.swap(n) for (i <- 0 to n * rateA) out.write(Env.BEER) }}
val borice = new SThread("RS-Borice") { override def swap(n: Int) { super.swap(n) for (i <- 0 to n * rateB) in.read() }}
val out = new PipedOutputStream()val in = new PipedInputStream(out, size)
INCREASE
Java IOval alice = new SThread("RS-Alice") { override def swap(n: Int) { super.swap(n) for (i <- 0 to n * rateA) out.write(Env.BEER) }}
val borice = new SThread("RS-Borice") { override def swap(n: Int) { super.swap(n) for (i <- 0 to n * rateB) in.read() }}
val out = new PipedOutputStream()val in = new PipedInputStream(out, size)
INCREASE
Blocking is only a matter of time :(
Java IO
Java NIO“There is a better way”
Java NIOA SinkChannel BSourceChannel
Write
Block
Transfer
BufferBuffer
Read
Block
Read
Fill
Java NIOval alice = new SThread("RS-Alice") { override def swap(n: Int) { val cnt = n * rateA val buffer = ByteBuffer.allocate(cnt) buffer.put(Vector.fill(cnt)(BEER).toArray) buffer.flip() val written = sinkChannel.write(buffer) super.swap(written) }}
val borice = new SThread("RS-Borice") { override def swap(n: Int) { val buffer = ByteBuffer.allocate(n * rateB) val cnt = sourceChannel.read(buffer) super.swap(cnt) }}
val pipe = Pipe.open()val sinkChannel = pipe.sinkval sourceChannel = pipe.sourcesourceChannel.configureBlocking(true) sinkChannel.configureBlocking(false)
BLOCKING
Java NIOval alice = new SThread("RS-Alice") { override def swap(n: Int) { val cnt = n * rateA val buffer = ByteBuffer.allocate(cnt) buffer.put(Vector.fill(cnt)(BEER).toArray) buffer.flip() val written = sinkChannel.write(buffer) super.swap(written) }}
val borice = new SThread("RS-Borice") { override def swap(n: Int) { val buffer = ByteBuffer.allocate(n * rateB) val cnt = sourceChannel.read(buffer) super.swap(cnt) }}
val pipe = Pipe.open()val sinkChannel = pipe.sinkval sourceChannel = pipe.sourcesourceChannel.configureBlocking(false) sinkChannel.configureBlocking(false)
NON-BLOCKING
“Choose how to fail”
Java NIO
IO & NIO
IO & NIO
Blocking
Dropping
or
Unbounded
or
Non-Determinism
OutOfMemory
Scalability
Solution
Backpressure
Solution
Backpressure
Dynamic Push/Pull
Fast Boris
Solution
Backpressure
Dynamic Push/Pull
Fast Alice
BackpressureA BStream
Data
Demand
Data
Demand
Java 8
Java 8public class ConsumerBorice extends SThread implements Consumer<byte[]> { public ConsumerBorice(String name) { super(name); } @Override public Consumer andThen(Consumer after) { return after; } @Override public void accept(byte[] bytes) { super.swap(bytes.length); } }
Java 8private static final ConsumerBorice borice =
new ConsumerBorice("RS-Borice") { public void swap(int count) { }};
private static final SThread alice = new SThread ("RS-Alice") { byte[] items(int count) { byte[] result = new byte[count]; Arrays.fill(result, Env.BEER()); return result; } public void swap(int count) { Stream.of(items(count)).parallel() .filter(s -> s.equals(Env.BEER())) .forEach(borice); super.swap(count); } };
Java 8private static final ConsumerBorice borice =
new ConsumerBorice("RS-Borice") { public void swap(int count) { }};
private static final SThread alice = new SThread ("RS-Alice") { byte[] items(int count) { byte[] result = new byte[count]; Arrays.fill(result, Env.BEER()); return result; } public void swap(int count) { Stream.of(items(count)).parallel() .filter(s -> s.equals(Env.BEER())) .forEach(borice); super.swap(count); } }; transformation
static push (or pull)
or sequential
Java 8private static final ConsumerBorice borice =
new ConsumerBorice("RS-Borice") { public void swap(int count) { }};
private static final SThread alice = new SThread ("RS-Alice") { byte[] items(int count) { byte[] result = new byte[count]; Arrays.fill(result, Env.BEER()); return result; } public void swap(int count) { Stream.of(items(count)).parallel() .filter(s -> s.equals(Env.BEER())) .forEach(borice); super.swap(count); } }; Synchronous
Non-Deterministic
Limited Scalability
Terminates on error
Problems
Synchronous
Non-Deterministic
Limited Scalability
Terminates on error
Problems
Synchronous
Non-Deterministic
Limited Scalability Terminates on error
Message Driven
Responsive
Elastic Resilient
Reactive
Message Driven
Responsive
Elastic Resilient
http://www.reactivemanifesto.org
Reactive Streams
… a standard for asynchronous stream processing with non-
blocking backpressure.
http://www.reactive-streams.org
ParticipantsNetflix rxJava, rxScala, …
Oracle
Pivotal Spring Reactor
RedHat Vert.x
Typesafe Akka Streams
Ratpack
Reactive Streams
• Semantics (Specification)
• API (Application Programming Interface)
• TCK (Technology Compatibility Kit)
APIpublic interface Publisher<T> { void subscribe(Subscriber<? super T> var1); }
public interface Subscriber<T> { void onSubscribe(Subscription var1); void onNext(T var1); void onError(Throwable var1); void onComplete(); }
public interface Subscription { void request(long var1); void cancel(); }
Subscription
Subscription
subscribe
onSubscribe
Producer Subscriber
Streaming
request
Subscription
onNext
onComplete
onError
cancel
Subscriber
trait BytePublisher extends Publisher[Byte] { var subscriber: Subscriber[_ >: Byte] = _ override def subscribe(subscriber: Subscriber[_ >: Byte]) { this.subscriber = subscriber }}
val alice = new SThread("RS-Alice") with BytePublisher {
override def swap(n: Int) { for { i <- 1 to n } subscriber.onNext(Env.BEER) super.swap(n) }
}
Publisher
trait ByteSubscriber[T >: Byte] extends Subscriber[T] { var subscription: Subscription = _ override def onSubscribe(subscription: Subscription) { this.subscription = subscription } override def onError(t: Throwable) { } override def onComplete() { }}
val borice = new SThread(“RS-Borice") with ByteSubscriber[Byte]{
def onNext(t: Byte) { super.swap(1) }}
Subscriber
val borice = new SThread(“RS-Borice") with ByteSubscriber[Byte]{ alice.subscribe(this) override def swap(n: Int) { subscription.request(n) } def onNext(t: Byte) { super.swap(1) }}
Right Subscriber
val alice = new SThread("RS-Alice") with BytePublisher { override def swap(n: Int) { val cnt = math.min(n, counter.get()) counter.addAndGet(-cnt) for { i <- 1 to cnt } subscriber.onNext(Env.BEER) super.swap(n) }}
Right Publishertrait BytePublisher extends Publisher[Byte] { var subscriber: Subscriber[_ >: Byte] = _ var subscription: Subscription = _ val counter = new AtomicInteger(0) def request(l: Long): Unit = { counter.addAndGet(l.toInt) } override def subscribe(subscriber: Subscriber[_ >: Byte]) { this.subscription = new ByteSub(this) this.subscriber = subscriber subscriber.onSubscribe(this.subscription) }}
implicit val mat = FlowMaterializer()(system)Source(alice).runWith(Sink(borice))
Akka Streams
Streams.create(alice).subscribe(borice)
Reactor Stream
ratpack.stream.Streams.buffer(alice).subscribe(borice)
Ratpack.io
import rx.RxReactiveStreams._ subscribe(toObservable(alice), borice)
RxReactiveStreams
//vert.x 3.0 - only supports Streams[Buffer] val rws = ReactiveWriteStream.writeStream()rws.subscribe(borice)val rrs = ReactiveReadStream.readStream() alice.subscribe(rrs)val pump = Pump.pump(rrs, rws)pump.start()
Vert.X
Why should I care?
Because
•Simplifies reactive programming•Rises program’s abstraction level•May be future JDK standard
Abstraction levels
Abstraction levels
Runnable & Thread
Abstraction levels
java.util.concurrent
Abstraction levels
Promise & Future
Abstraction levels
Actors
Abstraction levels
Streams
Abstraction levels
Interoperability
Shop Admin
Delivery
WebShop
Merchant
ADs Stats
Shop Admin
Delivery
WebShop
Merchant
ADs Stats
DB
REST
File System
Messaging
REST
Shop Admin(vert.x)
Delivery(Spring)
WebShop(Akka)
Merchant(ratpack.io)
ADs(Rx…)
Stats(Spray)
DB
REST
File System
Messaging
REST
Shop Admin(vert.x)
Delivery(Spring)
WebShop(Akka)
Merchant(ratpack.io)
ADs(Rx…)
Stats(Spray)
DB
Shop Admin(vert.x)
Data Flow
Backpressure
Delivery(Spring)
WebShop(Akka)
Merchant(ratpack.io)
ADs(Rx…)
Stats(Spray)
DB
Example Akka
Example Akka
val display = { def ellipse (i: Input) = setColor(i).fillOval(i(3), i(4), i(5), i(6)) def rect (i: Input) = setColor(i).fillRect(i(3), i(4), i(5), i(6)) val logics: Seq[(Input) => Unit] = Seq(ellipse, rect) def rnd = Random.nextInt(255) val timer = Source(0.seconds, 1.second, () => rnd ) val randoms = Source { () => Some(rnd) } val functions = timer map { i => logics(i % logics.size) } val display = functions map { f => val groups = randoms.take(7) val params = groups.fold(List.empty[Int])((l, i) => i :: l) for { p <- params } f(p) } display} def start = { display.runWith(BlackholeSink)}
Example Akka
Use Case
Price Correction System
Product schedulesCompetitors
Volatile sourcesAudit
Historical trendsAlerts
Sales HeuristicsPricing Rules
Adjustments
CompetitorsVolatile sources
Audit
Historical trendsAlerts
Sales HeuristicsPricing Rules
Adjustments
Timer Scheduler
Volatile sourcesAudit
Historical trendsAlerts
Sales HeuristicsPricing Rules
Adjustments
Timer Scheduler Robots
Audit
Historical trendsAlerts
Sales HeuristicsPricing Rules
Adjustments
Validators
Timer Scheduler Robots
Historical trendsAlerts
Sales HeuristicsPricing Rules
Adjustments
ValidatorsLogger
Timer Scheduler Robots
Historical trendsAlerts
Pricing RulesAdjustments
Stock ValidatorsLogger
Timer Scheduler Robots
Historical trendsAlerts
Adjustments
Stock ValidatorsLogger
Timer Scheduler Robots
Pricing
Historical trendsAlerts
Stock ValidatorsLogger
Timer Scheduler Robots
Pricing Ruler
Historical trends
Stock ValidatorsLogger
Timer Scheduler Robots
Pricing Ruler Safety
Stock ValidatorsLogger
Timer Scheduler Robots
Pricing Ruler Safety
Archive
Stock
Validators
Logger
Timer Scheduler
Robots
Pricing
RulerSafety
Archive
Data Flow
Backpressure
val schedules = Flow[Date] mapConcat generateSchedules val robots = Flow[Schedule] map scrape
val validations = Flow[ScrapeResult] map { site => logSite(site) validate(site)}
val pricing = Flow[Validation] map checkPrice
val stock = Flow[Validation] map checkStock
val ruler = Flow[(Price, Stock)] map applyRule
val safety = Flow[Rule] map checkSafety
val zip = Zip[Price, Stock] val split = Broadcast[Validation]
Stock
Validators
Logger
Timer Scheduler
Robots
Pricing
RulerSafety
Archive
Data Flow
Backpressure
val timer = Source(0.seconds, 1.minute, () => now) val archive = ForeachSink[SafetyCheck] { logRule }
val graph = FlowGraph { implicit builder => timer ~> schedules ~> robots ~> validations ~> split split ~> stock ~> zip.right split ~> pricing ~> zip.left ~> ruler ~> safety ~> archive} graph.run()
24 cores
48 Gb
24 cores
48 Gb150 000 000
positions daily
Reactive Streams
Reactive Streams
•Simplify reactive programming•Rise program’s abstraction level•May be future JDK standard
Now I care!
Thank you
https://github.com/slavaschmidt/reactive_streams_talk
@slavaschmidt