Java/Scala Lab: Slava Schmidt - Introduction to Reactive Streams

Post on 11-Jul-2015

244 views 3 download

Tags:

Transcript of Java/Scala Lab: Slava Schmidt - Introduction to Reactive Streams

Reactive

Streams

Reactive

Streams

THEN Java Developer

LATER Enterprise Architect

NOW Scala Consultant

TWITTER @slavaschmidt

MAIL slavaschmidt@gmx.de

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

MEET ALICE AND BORIS

MEET ALICE AND BORIS

Java 6

Java 7

Java 8

NAK (NACK)

Backpressure

Reactive Stream

MEET ALICE 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 Scalable

Terminates on error

Problems

Synchronous

Non-Deterministic

Limited Scalable

Terminates on error

Problems

Synchronous

Non-Deterministic

Limited Scalable 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, …OraclePivotal Spring Reactor

RedHat Vert.xTwitter

Typesafe Akka StreamsRatpack

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(ratpack.io)

Merchant(Akka)

ADs(Rx…)

Stats(Spray)

DB

REST

File System

Messaging

REST

Shop Admin(vert.x)

Delivery(Spring)

WebShop(ratpack.io)

Merchant(Akka)

ADs(Rx…)

Stats(Spray)

DB

Shop Admin(vert.x)

Data Flow

Backpressure

Delivery(Spring)

WebShop(ratpack.io)

Merchant(Akka)

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

@slavaschmidt

slavaschmidt@gmx.de