38
Building Scalable Stateless Applications with RxJava Rick Warren | Principal Engineer, eHarmony [email protected] | @DangerLemming

Building Scalable Stateless Applications with RxJava

Embed Size (px)

DESCRIPTION

RxJava is a lightweight open-source library, originally from Netflix, that makes it easy to compose asynchronous data sources and operations. This presentation is a high-level intro to this library and how it can fit into your application.

Citation preview

Page 1: Building Scalable Stateless Applications with RxJava

Building Scalable Stateless Applications with

RxJavaRick Warren | Principal Engineer, eHarmony [email protected] | @DangerLemming

Page 2: Building Scalable Stateless Applications with RxJava

RxJava• Encapsulates data sequences: Observable

• Provides composable operations on them

• Abstracts away threading and synch

• Port from Microsoft .NET Reactive Extensions

• Java 6+, Groovy, Clojure, JRuby, Kotlin, and Scala; Android-compatible

Page 3: Building Scalable Stateless Applications with RxJava

Netflix

Coarse-Grained API

Mi- -cro Ser- -vic- -es

ClientClient Client Client Client

Orchestration Layer

Page 4: Building Scalable Stateless Applications with RxJava

ApplicabilityApplication or Presentation-Layer Service

Data Source

Data Source

Data Source

Data Source

Data Source

ClientClientClient Client Client

Page 5: Building Scalable Stateless Applications with RxJava

3 Problems

1. Streaming queries

2. Rate-limited APIs (with retries)

3. Using Futures

Page 6: Building Scalable Stateless Applications with RxJava

Data Store ClientService

Streaming Queries

1. Query lots of data from NoSQL store

2. Enrich, filter, and score on the fly

3. Deliver “best” results to client

1 2 3

Page 7: Building Scalable Stateless Applications with RxJava

Streaming Queries

1Query

2Enrich

3Deliver

+ =

Too Much Latency

Page 8: Building Scalable Stateless Applications with RxJava

Streaming Queries

&& =

1Query

2Enrich

3Deliver

More Efficient

Page 9: Building Scalable Stateless Applications with RxJava

Data Store Client

Streaming QueriesIterables

ReadEnri

chSco

reDrai

n

• Iterator pattern for fast start and lower footprint • Composed as Decorators for flexibility • Limitation: Doesn’t abstract timing, concurrency

Page 10: Building Scalable Stateless Applications with RxJava

Rate-Limited APIsScenario: Ingest data from public API—they will refuse rapid requests!

YouTube, Facebook,

etc.

Your Service

Page 11: Building Scalable Stateless Applications with RxJava

Rate-Limited APIsYouTube,

Facebook, etc.

Your Service

Insight: The module responsible for what data is available is also responsible for when data is available

Page 12: Building Scalable Stateless Applications with RxJava

Rate-Limited APIsYouTube,

Facebook, etc.

Your Service

Solution: Facade API with augmented Visitors to encapsulate timing and retries

interface  DataVisitor<T>  extends  java.io.Closeable  {      void  accept(T  data);  }

Limitation: Scheduling doesn't generalize

Page 13: Building Scalable Stateless Applications with RxJava

Using Futures

• API and implementation cluttered with concurrency variants • Caller responsible for concurrency, but has least information

• What can you do with a Future besides block?

javax.ws.rs.client.Invocation  

Response  invoke()  

Future<Response>  submit()

Page 14: Building Scalable Stateless Applications with RxJava

Using FuturesListeningExecutorService  service  =    MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));  

ListenableFuture<Explosion>  explosion  =  service.submit(new  Callable<Explosion>()  {      public  Explosion  call()  {          return  pushBigRedButton();      }  });  Futures.addCallback(explosion,  new  FutureCallback<Explosion>()  {      public  void  onSuccess(Explosion  explosion)  {          walkAwayFrom(explosion);      }      public  void  onFailure(Throwable  thrown)  {          battleArchNemesis();      }  });

What about Guava’s ListenableFuture?

• Requires access to ExecutorService! • Still hard to compose • Doesn’t generalize to multiple results

Page 15: Building Scalable Stateless Applications with RxJava

Make ItWork

Interactions should: • Support multiple values • Be efficient & incremental • Encapsulate timing • Compose operations

Page 16: Building Scalable Stateless Applications with RxJava

RxJava: Observable• Like Iterable, but offers fluent chaining operations

• Like Stream,but supports async termination and error handling

Page 17: Building Scalable Stateless Applications with RxJava

Iterable Exampletry  {  

for  (E  elem  :  elements)  {  onNext(elem);  

}  onCompleted();  

}  catch  (Throwable  ex)  {  onError(ex);  

}

Page 18: Building Scalable Stateless Applications with RxJava

Iterable Exampletry  {  

elements.forEach(elem  -­‐>  onNext(elem)  

);  onCompleted();  

}  catch  (Throwable  ex)  {  onError(ex);  

}

First-class Visitor (Consumer)

Page 19: Building Scalable Stateless Applications with RxJava

Stream Exampletry  {  

elements.parallelStream()  .filter(condition)  .map(transformation)  .forEach(elem  -­‐>  onNext(elem));  

onCompleted();  }  catch  (Throwable  ex)  {  

onError(ex);  }

Still serial

Parallelize operations

Page 20: Building Scalable Stateless Applications with RxJava

Observable Exampleelements  

.filter(condition)  

.map(transformation)  

.subscribe(  elem  -­‐>  onNext(elem),  ex  -­‐>  onError(ex),  ()  -­‐>  onCompleted());

Fully async’able

Page 21: Building Scalable Stateless Applications with RxJava

Creating an Observableimport  rx.*;  

Observable<String>  o  =  Observable.just(    "a",  "b",  "c");  

Iterable<String>  it  =  ImmutableList.of(    "a",  "b",  "c");Observable<String>  o  =  Observable.from(it);  

Future<String>  f  =  exec.submit(myTask);Observable<String>  o  =  Observable.from(    f,    Schedulers.from(exec));

Page 22: Building Scalable Stateless Applications with RxJava

Example: JDBCpublic  final  class  ObservableDB  {          private  final  DataSource  db;  

       public  ObservableDB(final  DataSource  source)  {                  this.db  =  Objects.requireNonNull(source);          }  

       /**  Each  emitted  List  represents  a  row  in  the  ResultSet.  */          public  Observable<List<Object>>  select(                          final  String  sql,                            final  Iterable<?>  params)  {                  return  Observable.create(new  Observable.OnSubscribe<List<Object>>()  {                          @Override                          public  void  call(final  Subscriber<?  super  List<Object>>  sub)  {                                //  ...                          }                  });          }  }

Page 23: Building Scalable Stateless Applications with RxJava

Example: JDBC@Override  public  void  call(final  Subscriber<?  super  List<Object>>  sub)  {        try  {                  try  (Connection  cx  =  db.getConnection();                                    PreparedStatement  stmt  =  cx.prepareStatement(sql))  {                          int  i  =  0;  for  (final  Object  p  :  params)  {  stmt.setObject(i++,  p);  }                          try  (ResultSet  results  =  stmt.executeQuery())  {                                while  (results.next()  &&  !sub.isUnsubscribed())  {                                          final  Lists<Object>  row  =  new  ArrayList<>();                                          for  (int  col  =  0;  col  <  results.getMetaData().getColumnCount();  ++col)  {                                                  row.add(results.getObject(col));                                          }                                          sub.onNext(row);                                  }                          }                  }                  if  (!sub.isUnsubscribed())  {                          sub.onCompleted();                  }          }  catch  (final  Exception  ex)  {                  if  (!sub.isUnsubscribed())  {                          sub.onError(ex);                  }          }  }

Serialize

Call n times

Call one or the other, once

Page 24: Building Scalable Stateless Applications with RxJava

Example: JDBCpublic  void  printProductCatalog(                  final  DataSource  source,                    final  boolean  awesome)  {          ObservableDB  db  =  new  ObservableDB(source);          Subscription  subscription  =  db.select(                          "SELECT  *  FROM  products  WHERE  isAwesome=?",                            Collections.singleton(awesome))                  //  Func1<List<Object>,  Product>  :                  .map(unmarshallRowIntoProduct())                  //  Func1<Product,  Observable<ProductWithPrice>>  :                  .flatMap(remoteLookupPriceForProduct(this.priceService))                  .take(NUM_PAGES  *  NUM_PRODUCTS_PER_PAGE)                  .window(NUM_PAGES)                  .subscribe(new  Observer<Observable<ProductWithPrice>>()  {                          ...                  });          //  Some  time  later,  if  we  change  our  minds:          //subscription.unsubscribe();  }

interface  PriceService  {      Observable<ProductWithPrice>            getPrice(Product  p);  }

Page 25: Building Scalable Stateless Applications with RxJava

Example: JDBC.subscribe(new  Observer<Observable<ProductWithPrice>>()  {          private  final  AtomicInteger  pageNumber  =  new  AtomicInteger(1);  

       @Override          public  void  onNext(final  Observable<ProductWithPrice>  page)  {                  System.out.println("Page  "  +  pageNumber.getAndIncrement());                  page.forEach(new  Action1<ProductWithPrice>()  {                          @Override                          public  void  call(final  ProductWithPrice  product)  {                                  System.out.println("Product:"  +  product);                          }                  });          }  

       @Override          public  void  onError(final  Throwable  ex)  {                  System.err.println("This  is  how  you  handle  errors?  Srsly?");          }  

       @Override          public  void  onCompleted()  {                  System.out.println("Copyright  2014  ACME  Catalog  Company");          }  });  

Page 26: Building Scalable Stateless Applications with RxJava

OperationsUseful stuff, built in

Page 27: Building Scalable Stateless Applications with RxJava

Content Filtering• Observable<T>  filter(Func1<T,  Boolean>  predicate)  

• Observable<T>  skip(int  num)  

• Observable<T>  take(int  num)  

• Observable<T>  takeLast(int  num)  

• Observable<T>  elementAt(int  index)  

• Observable<T>  distinct()  

• Observable<T>  distinctUntilChanged()

Page 28: Building Scalable Stateless Applications with RxJava

Time Filtering• Observable<T>  throttleFirst(long  duration,  TimeUnit  unit)  

• Observable<T>  throttleLast(long  duration,  TimeUnit  unit)  

• Observable<T>  timeout(long  duration,  TimeUnit  unit)

Page 29: Building Scalable Stateless Applications with RxJava

Transformation• Observable<R>  map(Func1<T,  R>  func)  

• Observable<R>  flatMap(Func1<T,  Observable<R>>  func)  

• Observable<R>  cast(Class<R>  klass)  

• Observable<GroupedObservable<K,  T>>  groupBy(Func1<T,  K>  keySelector)

Page 30: Building Scalable Stateless Applications with RxJava

ConcurrencyEncapsulated, so usually you don’t care

Page 31: Building Scalable Stateless Applications with RxJava

Concurrency1. Single-threaded and synchronous by default:

RxJava doesn’t magically create new threads for you

2. When creating an Observable, invoke Subscriber from any thread you like (or use Actors, etc.)

3. Derive new Observables by binding subscription and/or observation to Schedulers

Page 32: Building Scalable Stateless Applications with RxJava

Rescheduling

Observable<T>  rescheduled  =  obs  .subscribeOn(mySubScheduler)  .observeOn(myObsScheduler);

Page 33: Building Scalable Stateless Applications with RxJava

What’s a Scheduler?

Policy for time-sharing among Workers

• Worker: Serial collection of scheduled Actions

• Action: Callable unit of work, e.g. Observable’s subscription or notification

Page 34: Building Scalable Stateless Applications with RxJava

Built-in Schedulers• Immediate—Schedulers.immediate()

• Run without scheduling in calling thread

• Trampoline—Schedulers.trampoline() • Enqueue Actions in thread-local priority queue

• Computation—Schedulers.computation() • Enqueue in event loop with as many threads as cores

Page 35: Building Scalable Stateless Applications with RxJava

Built-in Schedulers• New Thread—Schedulers.newThread()

• Each Worker is a single-thread ExecutorService

• I/O—Schedulers.io() • Mostly like New Thread, but with some pooling

• Executor Service—Schedulers.from(        ExecutorService)• Bind new Scheduler to arbitrary ExecutorService, with

external serialization of Actions. Observations may hop threads!

Page 36: Building Scalable Stateless Applications with RxJava

Bring Data to Calling Thread

myObservable.toBlocking()

Avoid whenever possible

Operations: • first()  • last()  • toFuture()  • toIterable()

Page 37: Building Scalable Stateless Applications with RxJava

Learn More:• Wiki: https://github.com/ReactiveX/RxJava/wiki

• JavaDoc: http://reactivex.io/RxJava/javadoc/

Page 38: Building Scalable Stateless Applications with RxJava

More Resources• Netflix API architecture: http://

techblog.netflix.com/2013/01/optimizing-netflix-api.html

• Jersey: https://jersey.java.net/apidocs/latest/jersey/index.html

• Guava ListenableFuture: https://code.google.com/p/guava-libraries/wiki/ListenableFutureExplained

• rxjava-jdbc: https://github.com/davidmoten/rxjava-jdbc/

• Email: [email protected]

• Twitter: @DangerLemming

• GitHub: https://github.com/rickbw

• LinkedIn: https://www.linkedin.com/in/rickbwarren