You are on page 1of 174

Reactive Streams with Rx

JavaOne - September 2014

Ben Christensen
Developer – Edge Engineering at Netflix
@benjchristensen
!
!
!
!
!
!
http://techblog.netflix.com/
RxJava
http://github.com/ReactiveX/RxJava
http://reactivex.io
Single Multiple

Sync T getData() Iterable<T> getData()

Async Future<T> getData() Observable<T> getData()


public interface Observer<T> {
public class Observable<T> {
public abstract void onCompleted()
public static <T> Observable<T> create(OnSubscribe<T> f)
public abstract void onError(Throwable e)
public Subscription subscribe(Observer<? super T> subscriber)
public abstract void onNext(T t)
}
}

Lazy
Cold or Hot *onNext
Asynchronous (onError | onCompleted)?
Push
public interface Observer<T> {
public class Observable<T> {
public abstract void onCompleted()
public static <T> Observable<T> create(OnSubscribe<T> f)
public abstract void onError(Throwable e)
public Subscription subscribe(Observer<? super T> subscriber)
public abstract void onNext(T t)
}
}

Lazy
Cold or Hot *onNext
Asynchronous (or Synchronous) (onError | onCompleted)?
Push (or Pull)
public interface Observer<T> {
public class Observable<T> {
public abstract void onCompleted()
public static <T> Observable<T> create(OnSubscribe<T> f)
public abstract void onError(Throwable e)
public Subscription subscribe(Observer<? super T> subscriber)
public abstract void onNext(T t)
}
}

Lazy
Cold or Hot *onNext
Asynchronous (or Synchronous) (onError | onCompleted)?
Push (or Pull)
public interface Subscriber<T> implements Observer<T> {
public class Observable<T> {
public abstract void onCompleted()
public static <T> Observable<T> create(OnSubscribe<T> f)
public abstract void onError(Throwable e)
public Subscription subscribe(Subscriber<? super T> subscriber)
public abstract void onNext(T t)
}
}

Lazy
Cold or Hot *onNext
Asynchronous (or Synchronous) (onError | onCompleted)?
Push (or Pull)
public abstract class Subscriber<T> implements Observer<T>, Subscription
public abstract void onCompleted()
public abstract void onError(Throwable e)
public abstract void onNext(T t)
!
public final void add(Subscription s)
public void setProducer(Producer producer)
}
!
public interface Producer {
public void request(long n);
}
public abstract class Subscriber<T> implements Observer<T>, Subscription
public abstract void onCompleted()
public abstract void onError(Throwable e)
public abstract void onNext(T t)
!
public final void add(Subscription s)
public void setProducer(Producer producer)
}
!
public interface Producer {
public void request(long n);
}
public abstract class Subscriber<T> implements Observer<T>, Subscription
public abstract void onCompleted()
public abstract void onError(Throwable e)
public abstract void onNext(T t)
!
public final void add(Subscription s)
public void setProducer(Producer producer)
}
!
public interface Producer {
public void request(long n);
}
public abstract class Subscriber<T> implements Observer<T>, Subscription
public abstract void onCompleted()
public abstract void onError(Throwable e)
public abstract void onNext(T t)
!
public final void add(Subscription s)
public void setProducer(Producer producer)
}
!
public interface Producer {
public void request(long n);
}
public abstract class Subscriber<T> implements Observer<T>, Subscription
public abstract void onCompleted()
public abstract void onError(Throwable e)
public abstract void onNext(T t)
!
public final void add(Subscription s)
public void setProducer(Producer producer)
}
!
public interface Producer {
public void request(long n);
“reactive pull”
} dynamic push/pull
for flow control (backpressure)
public class Observable<T> {
public static <T> Observable<T> create(OnSubscribe<T> f)
public Subscription subscribe(Subscriber<? super T> subscriber)
}

public static interface OnSubscribe<T> {


public void call(Subscriber<? super T> subscriber);
}

public abstract class Subscriber<T> implements Observer<T>, Subscription


public abstract void onCompleted()
public abstract void onError(Throwable e)
public abstract void onNext(T t)
!
public final void add(Subscription s)
public void setProducer(Producer producer)
}
!
public interface Producer {
public void request(long n);
}
public class Observable<T> {
public static <T> Observable<T> create(OnSubscribe<T> f)
public Subscription subscribe(Subscriber<? super T> subscriber)
}

public static interface OnSubscribe<T> {


public void call(Subscriber<? super T> subscriber);
}

public abstract class Subscriber<T> implements Observer<T>, Subscription


public abstract void onCompleted()
public abstract void onError(Throwable e)
public abstract void onNext(T t)
!
public final void add(Subscription s)
public void setProducer(Producer producer)
}
!
public interface Producer {
public void request(long n);
}
public class Observable<T> {
public static <T> Observable<T> create(OnSubscribe<T> f)
public Subscription subscribe(Subscriber<? super T> subscriber)
}

public static interface OnSubscribe<T> {


public void call(Subscriber<? super T> subscriber);
}

public abstract class Subscriber<T> implements Observer<T>, Subscription


public abstract void onCompleted()
public abstract void onError(Throwable e)
public abstract void onNext(T t)
!
public final void add(Subscription s)
public void setProducer(Producer producer)
}
!
public interface Producer {
public void request(long n);
}
public class Observable<T> { public abstract class Subscriber<T> implements Observer<T>, Subscription
public static <T> Observable<T> create(OnSubscribe<T> f) public abstract void onCompleted()
public Subscription subscribe(Subscriber<? super T> subscriber) public abstract void onError(Throwable e)
} public abstract void onNext(T t)
!
public static interface OnSubscribe<T> { public final void add(Subscription s)
public void call(Subscriber<? super T> subscriber); public void setProducer(Producer producer)
} }
!
public interface Producer {
public void request(long n);
}

Observable.create(subscriber -> {
subscriber.onNext("Hello world!");
synchronous
subscriber.onCompleted(); single value
}).forEach(System.out::println);
public class Observable<T> { public abstract class Subscriber<T> implements Observer<T>, Subscription
public static <T> Observable<T> create(OnSubscribe<T> f) public abstract void onCompleted()
public Subscription subscribe(Subscriber<? super T> subscriber) public abstract void onError(Throwable e)
} public abstract void onNext(T t)
!
public static interface OnSubscribe<T> { public final void add(Subscription s)
public void call(Subscriber<? super T> subscriber); public void setProducer(Producer producer)
} }
!
public interface Producer {
public void request(long n);
}

Observable.create(subscriber -> {
subscriber.onNext("Hello");
subscriber.onNext("world");
synchronous
subscriber.onNext("!"); multiple values
subscriber.onCompleted();
}).forEach(System.out::println);
public class Observable<T> { public abstract class Subscriber<T> implements Observer<T>, Subscription
public static <T> Observable<T> create(OnSubscribe<T> f) public abstract void onCompleted()
public Subscription subscribe(Subscriber<? super T> subscriber) public abstract void onError(Throwable e)
} public abstract void onNext(T t)
!
public static interface OnSubscribe<T> { public final void add(Subscription s)
public void call(Subscriber<? super T> subscriber); public void setProducer(Producer producer)
} }
!
public interface Producer {
public void request(long n);
}

Observable.create(subscriber -> {
try {
subscriber.onNext(doSomething());
subscriber.onCompleted();
} catch (Throwable e) {
subscriber.onError(e); error notification
}
}).subscribeOn(Schedulers.io()) asynchronous
.forEach(System.out::println);
single value
public class Observable<T> { public abstract class Subscriber<T> implements Observer<T>, Subscription
public static <T> Observable<T> create(OnSubscribe<T> f) public abstract void onCompleted()
public Subscription subscribe(Subscriber<? super T> subscriber) public abstract void onError(Throwable e)
} public abstract void onNext(T t)
!
public static interface OnSubscribe<T> { public final void add(Subscription s)
public void call(Subscriber<? super T> subscriber); public void setProducer(Producer producer)
} }
!
public interface Producer {
public void request(long n);
}

Observable.create(subscriber -> {
int i = 0;
while (!subscriber.isUnsubscribed()) {
synchronous
subscriber.onNext(i++); multiple values
}
}).take(10).forEach(System.out::println);
with unsubscribe
public class Observable<T> { public abstract class Subscriber<T> implements Observer<T>, Subscription
public static <T> Observable<T> create(OnSubscribe<T> f) public abstract void onCompleted()
public Subscription subscribe(Subscriber<? super T> subscriber) public abstract void onError(Throwable e)
} public abstract void onNext(T t)
!
public static interface OnSubscribe<T> { public final void add(Subscription s)
public void call(Subscriber<? super T> subscriber); public void setProducer(Producer producer)
} }
!
public interface Producer {
public void request(long n);
}

Observable.create(subscriber -> {
AtomicInteger i = new AtomicInteger();
flow control with
AtomicLong requested = new AtomicLong(); “reactive pull”
subscriber.setProducer(r -> {
if (requested.getAndAdd(r) == 0) {
backpressure
do {
synchronous if (subscriber.isUnsubscribed()) { break; }
subscriber.onNext(i.incrementAndGet());
multiple values } while (requested.decrementAndGet() > 0);
}
});
}).observeOn(Schedulers.newThread()) thread scheduling
.take(10).forEach(System.out::println);
public class Observable<T> { public abstract class Subscriber<T> implements Observer<T>, Subscription
public static <T> Observable<T> create(OnSubscribe<T> f) public abstract void onCompleted()
public Subscription subscribe(Subscriber<? super T> subscriber) public abstract void onError(Throwable e)
} public abstract void onNext(T t)
!
public static interface OnSubscribe<T> { public final void add(Subscription s)
public void call(Subscriber<? super T> subscriber); public void setProducer(Producer producer)
} }
!
public interface Producer {
public void request(long n);
}

Observable.create(subscriber -> {
AtomicInteger i = new AtomicInteger();
AtomicLong requested = new AtomicLong();
subscriber.setProducer(r -> {
if (requested.getAndAdd(r) == 0) {
do {
if (subscriber.isUnsubscribed()) { break; }
subscriber.onNext(i.incrementAndGet());
} while (requested.decrementAndGet() > 0);
}
});
}).observeOn(Schedulers.newThread())
.take(10).forEach(System.out::println);
public class Observable<T> { public abstract class Subscriber<T> implements Observer<T>, Subscription
public static <T> Observable<T> create(OnSubscribe<T> f) public abstract void onCompleted()
public Subscription subscribe(Subscriber<? super T> subscriber) public abstract void onError(Throwable e)
} public abstract void onNext(T t)
!
public static interface OnSubscribe<T> { public final void add(Subscription s)
public void call(Subscriber<? super T> subscriber); public void setProducer(Producer producer)
} }
!
public interface Producer {
public void request(long n);
}

Observable.from(iterable)
.observeOn(Schedulers.newThread())
.take(10).forEach(System.out::println);
finite
finite

infinite
finite

infinite

distributed
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
flatMap

 Observable<R>  b  =  Observable<T>.flatMap({  T  t  -­‐>    


       Observable<R>  r  =  ...  transform  t  ...  
       return  r;  
 })
flatMap

 Observable<R>  b  =  Observable<T>.flatMap({  T  t  -­‐>    


       Observable<R>  r  =  ...  transform  t  ...  
       return  r;  
 })
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
       Observable.zip(a,  b,  {  a,  b,  -­‐>    
           ...  operate  on  values  from  both  a  &  b  ...  
           return  [a,  b];  //  i.e.  return  tuple  
       })
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
an observable api:
class  VideoService  {  
     def  Observable<VideoList>  getPersonalizedListOfMovies(userId);  
     def  Observable<VideoBookmark>  getBookmark(userId,  videoId);  
     def  Observable<VideoRating>  getRating(userId,  videoId);  
     def  Observable<VideoMetadata>  getMetadata(videoId);  
}

Treat everything like a stream

finite
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user
1 -> {
// then fetch personal catalog
2
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
3
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
4
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
5
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
6
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
3
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
4
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
5
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) {
// first request User object
return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> {
// then fetch personal catalog
Observable<Map<String, Object>> catalog = new PersonalizedCatalogCommand(user).observe()
.flatMap(catalogList -> {
return catalogList.videos().<Map<String, Object>> flatMap(video -> {
Observable<Bookmark> bookmark = new BookmarkCommand(video).observe();
Observable<Rating> rating = new RatingsCommand(video).observe();
Observable<VideoMetadata> metadata = new VideoMetadataCommand(video).observe();
return Observable.zip(bookmark, rating, metadata, (b, r, m) -> {
return combineVideoData(video, b, r, m);
});
});
});
// and fetch social data in parallel
Observable<Map<String, Object>> social = new SocialCommand(user).observe().map(s -> {
return s.getDataAsMap();
});
// merge the results
return Observable.merge(catalog, social);
}).flatMap(data -> {
// output as SSE as we get back the data (no waiting until all is done)
return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data)));
});
}
infinite
private static Observable<GroupedObservable<TypeAndNameKey, Map<String, Object>>>
aggregateUsingPivot(Observable<GroupedObservable<InstanceKey, Map<String, Object>>> instanceStreams) {

return instanceStreams.map(instanceStream -> {
return createGroupedObservable(instanceStream.getKey(), instanceStream
.groupBy((Map<String, Object> json) -> {
return TypeAndNameKey.from(String.valueOf(json.get("type")), String.valueOf(json.get("name")));
}));
}).lift(OperatorPivot.create()).map(commandGroup -> {
// merge all instances per group into a single stream of deltas and sum them
return createGroupedObservable(commandGroup.getKey(),
commandGroup.flatMap(instanceGroup -> {
return instanceGroup.startWith(Collections.<String, Object> emptyMap())
.buffer(2, 1)
.map(StreamAggregator::previousAndCurrentToDelta)
.filter(data -> data != null && !data.isEmpty());
}).scan(new HashMap<String, Object>(), StreamAggregator::sumOfDelta)
.skip(1));
});
}
private static Observable<GroupedObservable<TypeAndNameKey, Map<String, Object>>>
aggregateUsingPivot(Observable<GroupedObservable<InstanceKey, Map<String, Object>>> instanceStreams) {

return instanceStreams.map(instanceStream -> {
return createGroupedObservable(instanceStream.getKey(), instanceStream
.groupBy((Map<String, Object> json) -> {
return TypeAndNameKey.from(String.valueOf(json.get("type")), String.valueOf(json.get("name")));
}));
}).lift(OperatorPivot.create()).map(commandGroup -> {
// merge all instances per group into a single stream of deltas and sum them
return createGroupedObservable(commandGroup.getKey(),
commandGroup.flatMap(instanceGroup -> {
return instanceGroup.startWith(Collections.<String, Object> emptyMap())
.buffer(2, 1)
.map(StreamAggregator::previousAndCurrentToDelta)
.filter(data -> data != null && !data.isEmpty());
}).scan(new HashMap<String, Object>(), StreamAggregator::sumOfDelta)
.skip(1));
});
}
private static Observable<GroupedObservable<TypeAndNameKey, Map<String, Object>>>
aggregateUsingPivot(Observable<GroupedObservable<InstanceKey, Map<String, Object>>> instanceStreams) {

return instanceStreams.map(instanceStream -> {
return createGroupedObservable(instanceStream.getKey(), instanceStream
.groupBy((Map<String, Object> json) -> {
return TypeAndNameKey.from(String.valueOf(json.get("type")), String.valueOf(json.get("name")));
}));
}).lift(OperatorPivot.create()).map(commandGroup -> {
// merge all instances per group into a single stream of deltas and sum them
return createGroupedObservable(commandGroup.getKey(),
commandGroup.flatMap(instanceGroup -> {
return instanceGroup.startWith(Collections.<String, Object> emptyMap())
.buffer(2, 1)
.map(StreamAggregator::previousAndCurrentToDelta)
.filter(data -> data != null && !data.isEmpty());
}).scan(new HashMap<String, Object>(), StreamAggregator::sumOfDelta)
.skip(1));
});
}
private static Observable<GroupedObservable<TypeAndNameKey, Map<String, Object>>>
aggregateUsingPivot(Observable<GroupedObservable<InstanceKey, Map<String, Object>>> instanceStreams) {

return instanceStreams.map(instanceStream -> {
return createGroupedObservable(instanceStream.getKey(), instanceStream
.groupBy((Map<String, Object> json) -> {
return TypeAndNameKey.from(String.valueOf(json.get("type")), String.valueOf(json.get("name")));
}));
}).lift(OperatorPivot.create()).map(commandGroup -> {
// merge all instances per group into a single stream of deltas and sum them
return createGroupedObservable(commandGroup.getKey(),
commandGroup.flatMap(instanceGroup -> {
return instanceGroup.startWith(Collections.<String, Object> emptyMap())
.buffer(2, 1)
.map(StreamAggregator::previousAndCurrentToDelta)
.filter(data -> data != null && !data.isEmpty());
}).scan(new HashMap<String, Object>(), StreamAggregator::sumOfDelta)
.skip(1));
});
}
data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":317,"rollingCountSuccess":327,"reportingHosts":1}

data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":1376,"rollingCountSuccess":1432,"reportingHosts":4}
data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":317,"rollingCountSuccess":327,"reportingHosts":1}

data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":1376,"rollingCountSuccess":1432,"reportingHosts":4}
data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":317,"rollingCountSuccess":327,"reportingHosts":1}

data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":1376,"rollingCountSuccess":1432,"reportingHosts":4}

data => {"rollingCountFallbackFailure":0,"rollingCountFallbackSuccess":


0,"propertyValue_circuitBreakerRequestVolumeThreshold":"20","propertyValue_circuitBreakerForceOpen":false,"propertyValue_metricsRol
lingStatisticalWindowInMilliseconds":"10000","latencyTotal_mean":32,"type":"HystrixCommand","rollingCountResponsesFromCache":
0,"rollingCountTimeout":0,"propertyValue_executionIsolationStrategy":"SEMAPHORE","instanceId":"34567","rollingCountFailure":
0,"rollingCountExceptionsThrown":0,"latencyExecute_mean":32,"isCircuitBreakerOpen":false,"errorCount":
0,"rollingCountSemaphoreRejected":0,"latencyTotal":{"0":0,"25":0,"50":12,"75":48,"90":68,"95":96,"99":192,"99.5":1452,"100":
1560},"requestCount":1376,"rollingCountCollapsedRequests":94516,"rollingCountShortCircuited":0,"latencyExecute":{"0":0,"25":0,"50":
12,"75":48,"90":68,"95":96,"99":192,"99.5":1452,"100":
1560},"propertyValue_circuitBreakerSleepWindowInMilliseconds":"5000","currentConcurrentExecutionCount":
0,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":"10","errorPercentage":0,"rollingCountThreadPoolRejected":
0,"propertyValue_circuitBreakerEnabled":true,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"propertyValue_request
CacheEnabled":true,"rollingCountFallbackRejection":0,"propertyValue_requestLogEnabled":true,"rollingCountSuccess":
1432,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":"10","propertyValue_circuitBreakerErrorThresholdPercentage":"5
0","propertyValue_circuitBreakerForceClosed":false,"name":"GetPredictions","reportingHosts":
4,"propertyValue_executionIsolationThreadPoolKeyOverride":"null","propertyValue_executionIsolationThreadTimeoutInMilliseconds":"100
0"}
Flow Control
Flow Control
(backpressure)
Observable.from(iterable).take(1000).map(i -> "value_" + i).subscribe(System.out::println);

no backpressure needed
Observable.from(iterable).take(1000).map(i -> "value_" + i).subscribe(System.out::println);

no backpressure needed
synchronous on same thread
(no queueing)
Observable.from(iterable).take(1000).map(i -> "value_" + i)
.observeOn(Schedulers.computation()).subscribe(System.out::println);

backpressure needed
Observable.from(iterable).take(1000).map(i -> "value_" + i)
.observeOn(Schedulers.computation()).subscribe(System.out::println);

backpressure needed
asynchronous
(queueing)
Flow Control Options
Block
(callstack blocking and/or park the thread)
Temporal Operators
(batch or drop data using time)
Observable.range(1, 1000000).sample(10, TimeUnit.MILLISECONDS).forEach(System.out::println);

110584
242165
544453
942880
Observable.range(1, 1000000).throttleFirst(10, TimeUnit.MILLISECONDS).forEach(System.out::println);

1
55463
163962
308545
457445
592638
751789
897159
Observable.range(1, 1000000).debounce(10, TimeUnit.MILLISECONDS).forEach(System.out::println);

1000000
Observable.range(1, 1000000).buffer(10, TimeUnit.MILLISECONDS)
.toBlocking().forEach(list -> System.out.println("batch: " + list.size()));
batch: 71141
batch: 49488
batch: 141147
batch: 141432
batch: 195920
batch: 240462
batch: 160410
/* The following will emit a buffered list as it is debounced */
// first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe
Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount();
// then we get the debounced version
Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS);
// then the buffered one that uses the debounced stream to demark window start/stop
Observable<List<Integer>> buffered = burstStream.buffer(debounced);
// then we subscribe to the buffered stream so it does what we want
buffered.toBlocking().forEach(System.out::println);

[0, 1, 2]
[0, 1, 2]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4]
[0, 1]
[]
/* The following will emit a buffered list as it is debounced */
// first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe
Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount();
// then we get the debounced version
Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS);
// then the buffered one that uses the debounced stream to demark window start/stop
Observable<List<Integer>> buffered = burstStream.buffer(debounced);
// then we subscribe to the buffered stream so it does what we want
buffered.toBlocking().forEach(System.out::println);

[0, 1, 2]
[0, 1, 2]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4]
[0, 1]
[]
/* The following will emit a buffered list as it is debounced */
// first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe
Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount();
// then we get the debounced version
Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS);
// then the buffered one that uses the debounced stream to demark window start/stop
Observable<List<Integer>> buffered = burstStream.buffer(debounced);
// then we subscribe to the buffered stream so it does what we want
buffered.toBlocking().forEach(System.out::println);

[0, 1, 2]
[0, 1, 2]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4]
[0, 1]
[]
/* The following will emit a buffered list as it is debounced */
// first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe
Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount();
// then we get the debounced version
Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS);
// then the buffered one that uses the debounced stream to demark window start/stop
Observable<List<Integer>> buffered = burstStream.buffer(debounced);
// then we subscribe to the buffered stream so it does what we want
buffered.toBlocking().forEach(System.out::println);

[0, 1, 2]
[0, 1, 2]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4]
[0, 1]
[]
/* The following will emit a buffered list as it is debounced */
// first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe
Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount();
// then we get the debounced version
Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS);
// then the buffered one that uses the debounced stream to demark window start/stop
Observable<List<Integer>> buffered = burstStream.buffer(debounced);
// then we subscribe to the buffered stream so it does what we want
buffered.toBlocking().forEach(System.out::println);

[0, 1, 2]
[0, 1, 2]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4]
[0, 1]
[]
/* The following will emit a buffered list as it is debounced */
// first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe
Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount();
// then we get the debounced version
Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS);
// then the buffered one that uses the debounced stream to demark window start/stop
Observable<List<Integer>> buffered = burstStream.buffer(debounced);
// then we subscribe to the buffered stream so it does what we want
buffered.toBlocking().forEach(System.out::println);

[0, 1, 2]
[0, 1, 2]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4]
[0, 1]
[]

https://gist.github.com/benjchristensen/e4524a308456f3c21c0b
Observable.range(1, 1000000).window(50, TimeUnit.MILLISECONDS)
.flatMap(window -> window.count())
.toBlocking().forEach(count -> System.out.println("num items: " + count));
num items: 477769
num items: 155463
num items: 366768
Observable.range(1, 1000000).window(500000)
.flatMap(window -> window.count())
.toBlocking().forEach(count -> System.out.println("num items: " + count));

num items: 500000


num items: 500000
Reactive Pull
(dynamic push-pull)
Push when consumer
keeps up with producer.
!

Switch to Pull
when consumer is slow.
!

Bound all* queues.


Push when consumer
keeps up with producer.
!

Switch to Pull
when consumer is slow.
!

Bound all* queues.


*vertically, not horizontally
Observable.create(subscriber -> {
// emit to subscriber
Observable }).subscribe(new Subscriber<String>() { Subscriber
!
public void onCompleted() {
// do stuff
}
!
public void onError(Throwable e) {
// handle error
}
!
public void onNext(String t) {
!
}
!
});
Observable.create(subscriber -> {
// emit to subscriber
observable.subscribe }).subscribe(new Subscriber<String>() {
!
public void onCompleted() {
// do stuff
}
!
public void onError(Throwable e) {
// handle error
}
!
public void onNext(String t) {
!
}
!
});
Observable.create(subscriber -> {
subscriber.setProducer(request -> {
observable.subscribe
});
subscriber.setProducer }).subscribe(new Subscriber<String>() {
!
public void onCompleted() {
// do stuff
}
!
public void onError(Throwable e) {
// handle error
}
!
public void onNext(String t) {
!
}
!
});
Observable.create(subscriber -> {
subscriber.setProducer(request -> {

});
observable.subscribe }).subscribe(new Subscriber<String>() {
!
subscriber.setProducer public void onStart() {
request(5);
producer.request(n) }
!
public void onCompleted() {
// do stuff
}
!
public void onError(Throwable e) {
// handle error
}
!
public void onNext(String t) {
!
}
!
});
Observable.create(subscriber -> {
subscriber.setProducer(request -> {
subscriber.onNext(t); // 5 times
});
observable.subscribe }).subscribe(new Subscriber<String>() {
!
subscriber.setProducer public void onStart() {
request(5);
producer.request(n) }
!

subscriber.onNext(t) … public void onCompleted() {


// do stuff
}
!
public void onError(Throwable e) {
// handle error
}
!
public void onNext(String t) {
!
}
!
});
Observable.create(subscriber -> {
subscriber.setProducer(request -> {
subscriber.onNext(t); // 5 times
});
observable.subscribe }).subscribe(new Subscriber<String>() {
!

subscriber.setProducer public void onStart() {


request(5);
}
producer.request(n) !
public void onCompleted() {
subscriber.onNext(t) … // do stuff
}

producer.request(n)
!
public void onError(Throwable e) {
// handle error
}
!
public void onNext(String t) {
doStuffWith(t);
if(requested—- == 0) request(5);
}
!
});
Observable.create(subscriber -> {
subscriber.setProducer(request -> {
… previous logic …
subscriber.onCompleted();
});
observable.subscribe }).subscribe(new Subscriber<String>() {
!
subscriber.setProducer public void onStart() {
request(5);
producer.request(n) }
!

subscriber.onNext(t) … public void onCompleted() {


// do stuff
}
producer.request(n) !
public void onError(Throwable e) {
// handle error
subscriber.onCompleted/onError }
!

unsubscribe public void onNext(String t) {


doStuffWith(t);
if(requested—- == 0) request(5);
}
!
});
Observable observable.subscribe Subscriber

Observable Subscriber
observable.subscribe
Observable.from(iterable) .observeOn(Schedulers.computation())

.subscribe(System.out::println);
Observable.from(iterable) .observeOn(Schedulers.computation())

.subscribe(System.out::println);
Observable observable.subscribe Subscriber

Observable Subscriber
observable.subscribe
Observable subscriber.setProducer Subscriber

Observable Subscriber
subscriber.setProducer
Observable producer.request(5) Subscriber
outstanding
requested = 5

Observable Subscriber
producer.request(n)
Observable producer.request(5) Subscriber
outstanding
requested = 5

Observable Subscriber
producer.request(n)
Observable producer.request(5) Subscriber
outstanding
requested = 5

Observable Subscriber
producer.request(n)
Observable subscriber.onNext Subscriber
outstanding
requested = 5

Observable Subscriber
Observable subscriber.onNext Subscriber
outstanding
requested = 5

Observable Subscriber
Observable subscriber.onNext Subscriber
outstanding
requested = 4

Observable Subscriber
Observable subscriber.onNext Subscriber
outstanding
requested = 3

Observable Subscriber
Observable producer.request(1) Subscriber
outstanding
requested = 3

request(1)

Observable subscriber.onNext Subscriber


Observable producer.request(1) Subscriber
outstanding
requested = 4

Observable subscriber.onNext Subscriber


Observable subscriber.onNext Subscriber
outstanding
requested = 4

Observable subscriber.onNext Subscriber


Observable Subscriber
outstanding
requested = 0

Observable subscriber.onNext Subscriber


Observable Subscriber
outstanding
requested = 0

Observable subscriber.onNext Subscriber


Observable Subscriber
outstanding
requested = 0

Observable subscriber.onNext Subscriber


Observable Subscriber
outstanding
requested = 0

Observable subscriber.onNext Subscriber


Observable Subscriber
outstanding
requested = 0

Observable subscriber.onNext Subscriber


Observable producer.request(2) Subscriber
outstanding
requested = 2

request(2)

Observable subscriber.onNext Subscriber


someObservable.subscribe(new Subscriber<T>() {
@Override
public void onStart() {
request(1);
}
!
@Override
public void onCompleted() {
// gracefully handle sequence-complete
}
!
@Override
public void onError(Throwable e) {
// gracefully handle error
}
!
@Override
public void onNext(T n) {
// do something with the emitted item "n"
// request another item:
request(1);
}
});
someObservable.subscribe(new Subscriber<T>() {
@Override
public void onStart() {
request(1);
}
!
@Override
public void onCompleted() {
// gracefully handle sequence-complete
}
!
@Override
public void onError(Throwable e) {
// gracefully handle error
}
!
@Override
public void onNext(T n) {
// do something with the emitted item "n"
// request another item:
request(1);
}
});
someObservable.subscribe(new Subscriber<T>() {
@Override
public void onStart() {
request(1);
}
!
@Override
public void onCompleted() {
// gracefully handle sequence-complete
}
!
@Override
public void onError(Throwable e) {
// gracefully handle error
}
!
@Override
public void onNext(T n) {
// do something with the emitted item "n"
// request another item:
request(1);
}
});
someObservable.subscribe(new Subscriber<T>() {
@Override
public void onStart() {
request(1024);
}
!
@Override
public void onCompleted() {
// gracefully handle sequence-complete
}
!
@Override
public void onError(Throwable e) {
// gracefully handle error
}
!
@Override
public void onNext(T n) {
enqueue(n);
// elsewhere the queue is drained
// and request(m) is called
}
!
});
someObservable.subscribe(new Subscriber<T>() {
@Override
public void onStart() {
request(1024);
}
!
@Override
public void onCompleted() {
// gracefully handle sequence-complete
}
!
@Override
public void onError(Throwable e) {
// gracefully handle error
}
!
@Override
public void onNext(T n) {
enqueue(n);
// elsewhere the queue is drained
// and request(m) is called
}
!
});
someObservable.subscribe(new Subscriber<T>() {
@Override
public void onStart() {
request(1024);
}
!
@Override
public void onCompleted() {
// gracefully handle sequence-complete
}
!
@Override
public void onError(Throwable e) {
// gracefully handle error
}
!
@Override
public void onNext(T n) {
enqueue(n);
// elsewhere the queue is drained
// and request(m) is called
}
!
});
Reactive Pull
hot vs cold?
Reactive Pull
… or when source doesn’t support request(n)?
hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);
hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);
hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);
hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);
hotSourceStream.onBackpressureDrop().observeOn(aScheduler);
stream.onBackpressure(strategy).subscribe
distributed
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
(movieId)

movieId=12345 movieId=34567
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
Stage 1 Stage 2
groupBy sink

Event Streams
Stage 1 Stage 2

12345

34567

56789
Stage 1 Stage 2

12345

34567

56789
Stage 1 Stage 2

12345

34567

56789
Stage 1 Stage 2

12345

56789

34567
Stage 1 Stage 2
34567
12345

56789

56789

34567
12345
Stage 1 Stage 2

12345
34567

56789

56789

12345
34567
Stage 1 Stage 2

12345

34567

56789
56789
12345

34567
Stage 1 Stage 2

12345 12345

56789 56789

34567 34567
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
10mins || 1000

4:40-4:50pm 5:00-5:07pm
(burst to 1000)
4:50-5:00pm 5:07-5:17pm
10mins || 1000

4:40-4:50pm 5:00-5:07pm
(burst to 1000)
4:50-5:00pm 5:07-5:17pm
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
flatMap
flatMap
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
MantisJob
.source(NetflixSources.moviePlayAttempts())
.stage(playAttempts -> {
return playAttempts.groupBy(playAttempt -> {
return playAttempt.getMovieId();
})
})
.stage(playAttemptsByMovieId -> {
playAttemptsByMovieId
.window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts
.flatMap(windowOfPlayAttempts -> {
return windowOfPlayAttempts
.reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> {
experiment.updateFailRatio(playAttempt);
experiment.updateExamples(playAttempt);
return experiment;
}).doOnNext(experiment -> {
logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis
}).filter(experiment -> {
return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get();
}).map(experiment -> {
return new FailReport(experiment, runCorrelations(experiment.getExamples()));
}).doOnNext(report -> {
logToHistorical("Failure report", report.getId(), report); // log for offline analysis
})
})
})
.sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)
stream.onBackpressure(strategy?).subscribe
stream.onBackpressure(buffer).subscribe
stream.onBackpressure(drop).subscribe
stream.onBackpressure(sample).subscribe
stream.onBackpressure(scaleHorizontally).subscribe
finite

infinite

distributed
Single Multiple

Sync T getData() Iterable<T> getData()

Async Future<T> getData() Observable<T> getData()


RxJava
http://github.com/ReactiveX/RxJava
http://reactivex.io
jobs.netflix.com

!
Reactive Programming in the Netflix API with RxJava http://techblog.netflix.com/2013/02/rxjava-netflix-api.html
!
Optimizing the Netflix API http://techblog.netflix.com/2013/01/optimizing-netflix-api.html
!
Reactive Extensions (Rx) http://www.reactivex.io
!
Reactive Streams https://github.com/reactive-streams/reactive-streams
!

RxJava !
!
RxJS
https://github.com/ReactiveX/RxJava http://reactive-extensions.github.io/RxJS/
!
@RxJava @ReactiveX
!

Ben Christensen
@benjchristensen