Hundreds of different types of services,
thousands of instances in multiple data centers.
Internet Load
Balancer
Profile
frontend
Company
frontend
Recruiter
frontend
Profile
backend
Search
backend
Company
backend
Recruiter
backend
Ads
backend
Data
Store
Data
Store
Data
Store
Data
Store
Most people are used to synchronous I/O when
making requests between servers
The most popular frameworks typically use
one-thread-per-request and blocking I/O
executeMethod blocks the thread until the
response comes back
void doGet(HttpServletRequest req, HttpServletResponse res) {
// Apache HttpClient
HttpClient client = new HttpClient();
GetMethod method = new GetMethod("www.example.com");
// executeMethod is a blocking, synchronous call
int statusCode = client.executeMethod(method);
System.out.println("Response " + statusCode);
}
MyServlet.java
http.request is a non-blocking call: the next
line executes before the response comes back
MyNodeApp.js
var callback = function(data) {
console.log("Response: " + data);
};
var options = {
hostname: 'www.google.com',
path: '/upload'
};
// Non-blocking HTTP call
http.request(options, callback);
console.log('This line may execute before the callback!');
void doGet(HttpServletRequest req, HttpServletResponse res) {
// Call a number of backend services to get data
Profile profile = profileSvc.getProfile();
Company company = companySvc.getCompany();
Skills skills = skillsSvc.getSkills();
}
MyServlet.java
Our services spend most of their time waiting
for data from other services and data stores
I/O is very expensive
http://www.eecs.berkeley.edu/~rcs/research/interactive_latency.html
In a threaded server, threads spend most of
the time idle, waiting on I/O
Threading dilemma
1. Creating new threads on the fly is expensive:
a. Use a thread pool
2. Too many threads in the thread pool:
a. Memory overhead
b. Context switching overhead
3. Too few threads in the thread pool:
a. Run out of threads, latency goes up
b. Sensitive to downstream latency!
Play is built on top of Netty, so it supports non-
blocking I/O
NIO benefits
1. No sensitivity to downstream slowness
2. Easy to parallelize I/O
3. Supports many concurrent and long-running
connections, enabling:
a. WebSockets
b. Comet
c. Server-Sent Events
This talk is a brief introduction to writing
asynchronous code with the Play Framework.
For each section, I will include simplified
examples: first in Java and then Scala.
Let's get a feel for Play by creating a
Java Controller
public class HelloWorld extends Controller {
public static Result index() {
return ok("Hello World");
}
}
Controllers are Java classes with methods that
return a Result, such as a 200 OK
app/controllers/HelloWorld.java
Don't worry about the use of static. Yes,
Play supports IOC. Using static (and other
shortcuts) lets me keep the examples simple.
public class HelloWorld extends Controller {
public static Result index(String name) {
return ok("Hello " + name);
}
}
Add a parameter
app/controllers/HelloWorld.java
public class HelloWorld extends Controller {
public static Result index(String name, int age) {
return ok("Hello " + name + " you are " + age +
" years old");
}
}
Add another parameter, this time an int
app/controllers/HelloWorld.java
GET /hello/:name/ :age controllers.HelloWorld.index(name: String, age: Int)
Add the parameter. Note the type checking!
conf/routes
@(name: String, age: Int)
<html>
<head></head>
<body>
<img src="/assets/images/play-logo.png"/>
<p>
Hello <b>@name</b>, you are <b>@age</b> years old
</p>
</body>
</html>
Add a view
app/views/hello.scala.html
public class HelloWorld extends Controller {
public static Result index(String name, int age) {
return ok(views.html.hello.render(name, age));
}
}
Render the view from the controller
app/controllers/HelloWorld.java
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
Let's use Play's Web Services library (WS) to
make some non-blocking HTTP calls
public class Proxy extends Controller {
public static Result index(String url) {
// Non blocking HTTP call
Promise<Response> responsePromise = WS.url(url).get();
// How do we turn a Promise into a Play Result?
}
}
app/controllers/Proxy.java
Create a new controller and use WS to make an
HTTP GET
(Play Framework source code)
Play has a built-in subclass of Result called
AsyncResult that takes a Promise<Result>
public static class AsyncResult implements Result {
private final Promise<Result> promise;
public AsyncResult(Promise<Result> promise) {
this.promise = promise;
}
}
public class Proxy extends Controller {
public static Result index(String url) {
Promise<Response> response = WS.url(url).get();
// Transform asynchronously into a Play Result
Promise<Result> result = response.map(toResult);
return async(result);
}
// A function that can transform a Response into a Result
private static Function<Response, Result> toResult =
new Function<Response, Result>() {
public Result apply(Response response) {
return ok(response.getBody()).as(("text/html");
}
};
}
app/controllers/Proxy.java
We can use the map method to turn a
Promise<Response> into a Promise<Result>
public class Proxy extends Controller {
public static Result index(String url) {
Logger.info("Before the HTTP call");
Promise<Response> response = WS.url(url).get();
Promise<Result> result = response.map(toResult);
Logger.info("After the HTTP call");
return async(result);
}
private static Function<Response, Result> toResult =
new Function<Response, Result>() {
public Result apply(Response response) {
Logger.info("Inside the toResult function");
return ok(response.getBody()).as("text/html");
}
};
}
app/controllers/Proxy.java
To see that it's non-blocking, add logging
Refresh the page and the logs show the
HTTP call really is non-blocking
object ProxyScala extends Controller {
def index(url: String) = Action {
val future: Future[Response] = WS.url(url).get()
// How do we turn a Future into a Play Result?
}
}
app/controllers/ProxyScala.scala
Create a new controller and use WS to make an
HTTP GET
(Play Framework source code)
Play has a built-in subclass of Result called
AsyncResult that takes a Future<Result>
case class AsyncResult(result: Future[Result]) extends
Result
// Convenience function to create an AsyncResult
def Async(promise: Promise[Result]) = AsyncResult(promise)
object ProxyScala extends Controller {
def index(url: String) = Action {
val future: Future[Response] = WS.url(url).get()
Async {
future.map { response =>
Ok(response.body).as("text/html")
}
}
}
}
app/controllers/ProxyScala.scala
We can use the map method to turn a Future
[Response] into a Future[Result]
val names = List("Jim", "Dean", "Kunal")
Let's start with a List of Strings
val names = List("Jim", "Dean", "Kunal")
def lower(str: String): String = str.toLowerCase
And a simple function that converts a String to
lowercase
List.map(f) will return a new List where
each element in the new List is the result of
calling f on each element of the original
List
val names = List("Jim", "Dean", "Kunal")
def lower(str: String): String = str.toLowerCase
names.map(lower)
// Output: List("jim", "dean", "kunal")
Mapping the lower function over names gives us
a new List where each name is lowercase
We saw map transform a List[String] into
a new List[String]. Can we transform a
List[X] into some other type List[Y]?
val names = List("Jim", "Dean", "Kunal")
def strlen(str: String): Int = str.length
Start with the same List, but now a new function
strlen that returns the length of a String
val names = List("Jim", "Dean", "Kunal")
def strlen(str: String): Int = str.length
names.map(strlen)
// Output: List(3, 4, 5)
Mapping strlen over names returns a new List
with the length of each String in names
Now we see that map can transform a List
[String] into a new List[Int].
class List[A] {
def map[B](f: A => B): List[B]
}
More generally, this is map's signature
val names = List("Jim", "Dean", "Kunal")
def explode(str: String): List[Char] = str.toCharArray.
toList
Same List, but now a new function explode that
returns a List of Characters in a String
val names = List("Jim", "Dean", "Kunal")
def explode(str: String): List[Char] = str.toCharArray.
toList
names.map(explode)
// Output:
// List(List(J, i, m), List(D, e, a, n), List(K, u, n, a,
l))
If we map explode over names, we get nested
Lists. But what if we want just one, flat List?
class List[A] {
def flatMap[B](f: A => List[B]): List[B]
}
We can use flatMap , which will combine (flatten)
any nested Lists
val names = List("Jim", "Dean", "Kunal")
def explode(str: String): List[Char] = str.toCharArray.
toList
names.flatMap(explode)
// Output: List(J, i, m, D, e, a, n, K, u, n, a, l)
Using flatMap gives us a single List with each
individual character
map and flatMap are defined on any
"collection" or "container": List, Set, Map, etc
val namesSet = Set("Jim", "Dean", "Kunal")
def explode(str: String): List[Char] = str.toCharArray.
toList
namesSet.flatMap(explode)
// Output: Set(e, n, J, u, a, m, i, l, K, D)
Using flatMap on a Set
val future: Future[Response] = WS.url(url).get()
val future: Future[Result] = future.map { response =>
Ok(response.body).as("text/html")
}
This is why it makes sense to use map to turn a
Future[Response] into a Future[Result]
The "container" class controls when the function
passed to map or flatMap actually gets
applied!
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
Making I/O requests in parallel is essential for
performance in a Service Oriented Architecture
With non-blocking I/O, parallel is the default
// These 3 HTTP requests will execute in parallel
Promise<Response> yahoo = WS.url("http://yahoo.com").get();
Promise<Response> google = WS.url("http://google.com").get();
Promise<Response> bing = WS.url("http://bing.com").get();
First, define a function that makes makes an HTTP
GET and returns a Promise with timing info
public Promise<Timing> timed(final String url) {
final long start = System.currentTimeMillis();
return WS.url(url).get().map(new Function<Response, Timing>() {
public Timing apply(Response response) throws Throwable {
return new Timing(url, System.currentTimeMillis() - start);
}
});
}
public class Timing {
public String url;
public long latency;
}
Next, make a controller that fires 3 requests in
parallel using the timed function we just created
public class Parallel extends Controller {
public static Result index() {
final Promise<Timing> yahoo = timed("http://www.yahoo.com");
final Promise<Timing> google = timed("http://www.google.com");
final Promise<Timing> bing = timed("http://www.bing.com");
}
}
Compose the 3 Promises into a single Promise
that will redeem when all 3 are done
public class Parallel extends Controller {
public static Result index() {
final Promise<Long> yahoo = timed("http://www.yahoo.com");
final Promise<Long> google = timed("http://www.google.com");
final Promise<Long> bing = timed("http://www.bing.com");
Promise<List<Timing>> all = Promise.waitAll(yahoo, google, bing);
}
}
Render the results as JSON
public class Parallel extends Controller {
public static Result index() {
final Promise<Timing> yahoo = timed("http://www.yahoo.com");
final Promise<Timing> google = timed("http://www.google.com");
final Promise<Timing> bing = timed("http://www.bing.com");
Promise<List<Timing>> all = Promise.waitAll(yahoo, google, bing);
return async(all.map(new Function<List<Timing>, Result>() {
public Result apply(List<Timing> timings) throws Throwable {
return ok(Json.toJson(timings));
}
}));
}
}
Once again, define a function that makes an HTTP
GET and returns a Future with timing info
def timed(url: String): Future[Timing] = {
val start = System.currentTimeMillis()
WS.url(url).get().map(_ =>
Timing(url, System.currentTimeMillis() - start)
)
}
case class Timing(url: String, latency: Long)
Next, make a controller that fires 3 requests in
parallel using the timed function we just created
object ParallelScala extends Controller {
def index = Action {
val yahoo = timed("http://www.yahoo.com")
val google = timed("http://www.google.com")
val bing = timed("http://www.bing.com")
}
}
Compose the 3 Futures into a single Future that
will redeem when all 3 are done
object ParallelScala extends Controller {
def index = Action {
val yahoo = timed("http://www.yahoo.com")
val google = timed("http://www.google.com")
val bing = timed("http://www.bing.com")
val all = Future.sequence(Seq(yahoo, google, bing))
}
}
Render the results as JSON
object ParallelScala extends Controller {
def index = Action {
val yahoo = timed("http://www.yahoo.com")
val google = timed("http://www.google.com")
val bing = timed("http://www.bing.com")
val all = Future.sequence(Seq(yahoo, google, bing))
Async {
all.map(timings => Ok(Json.toJson(timings)))
}
}
}
If parallel is the default, how do you do
sequential steps that depend on each other?
Example: make a request to duckduckgo's
instant answer API (step 1) and proxy an image
from the response (step 2)
First, call make a non-blocking call to duckduckgo
public class LuckyImage extends Controller {
public static Result index(String query) {
Promise<Response> duck = WS.url("http://www.duckduckgo.com")
.setQueryParameter("q", query)
.setQueryParameter("format", "json")
.get();
}
}
As a first step, we'll just proxy the response
public class LuckyImage extends Controller {
public static Result index(String query) {
Promise<Response> duck = WS.url("http://www.duckduckgo.com")
.setQueryParameter("q", query)
.setQueryParameter("format", "json")
.get();
return async(duck.map(new Function<Response, Result>() {
public Result apply(Response response) throws Throwable {
return ok(response.getBodyAsStream())
.as(response.getHeader("Content-Type"));
}
}));
}
}
As the second step, get the image URL from the
response and proxy just that.
Promise<Response> duck = // ... (same request as before)
return async(duck.flatMap(new Function<Response, Promise<Result>>(){
public Promise<Result> apply(Response response) {
String url = getImageUrlFromResponse(response);
return WS.url(url).get().map(new Function<Response, Result>() {
public Result apply(Response response) {
return ok(response.getBodyAsStream())
.as(response.getHeader("Content-Type"));
}
});
}
}));
ParSeq is a framework that makes it easier to
write asynchronous code in Java
Wrap asynchronous work in ParSeq Task objects,
which are similar to Promises and Futures
Task<Response> yahoo = Tasks.wrap(WS.url("http://www.yahoo.com"))
Task<Response> google = Tasks.wrap(WS.url("http://www.google.com"))
Task<Response> bing = Tasks.wrap(WS.url("http://www.bing.com"))
Use Tasks.par to compose tasks in parallel
Task<Response> yahoo = Tasks.wrap(WS.url("http://www.yahoo.com"))
Task<Response> google = Tasks.wrap(WS.url("http://www.google.com"))
Task<Response> bing = Tasks.wrap(WS.url("http://www.bing.com"))
// Create a new Task that will run all 3 tasks above at the same time
// and redeem when they are all done
Task<?> parallel = Tasks.par(yahoo, google, bing)
Use Tasks.seq to compose tasks sequentially
Task<Response> step1 = new Task() { ... }
Task<Response> step2 = new Task() { ... }
Task<Response> step3 = new Task() { ... }
// Create a new Task that will run the tasks above one at a time,
// in the order specified, and complete with the return value of the
// last one
Task<Response> sequential = Tasks.seq(task1, task2, task3)
ParSeq makes async code declarative and easier
to reason about: you can read it top to bottom!
Task<Result> complex = Tasks.seq(
Tasks.par(profileTask, companyTask, skillsTask),
Tasks.par(recommendedJobsTask, wvmxTask),
Tasks.par(hydrateImagesTask, hydrateJobsTask, hydrateSkillsTask)
Tasks.seq(assemblePageTask, fireTrackingTask)
)
We'll soon be open sourcing a plugin soon to
make it easy to use ParSeq in Play.
Syntactic sugar built into Scala. Translates into
map and flatMap calls without the nesting.
Instead of this...
aFuture.flatMap { a =>
bFuture.flatMap { b =>
cFuture.flatMap { c =>
dFuture.map { d =>
// Build a result using a, b, c, d
}
}
}
}
Use this. Note that this syntax works for any object
with map and flatMap methods.
for {
a <- aFuture
b <- bFuture
c <- cFuture
d <- dFuture
} yield {
// Build a result using a, b, c, d
}
Sequential async I/O example. Note that each step
can refer to previous ones.
for {
a <- WS.url(...).get()
b <- WS.url(a).get()
c <- WS.url(a + b).get()
d <- WS.url(a + b + c).get()
} yield {
// Build a result using a, b, c, d
}
Parallel async I/O example. Only difference is that
the async calls are outside the for statement.
val futureA = WS.url(...)
val futureB = WS.url(...)
val futureC = WS.url(...)
val futureD = WS.url(...)
for {
a <- futureA
b <- futureB
c <- futureC
d <- futureD
} yield {
// Build a result using a, b, c, d
}
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
With a single server, the odds of hitting an error
are relatively low
In a distributed system with thousands of servers,
the odds that you hit an error are very high
Internet Load
Balancer
Profile
frontend
Company
frontend
Recruiter
frontend
Profile
backend
Search
backend
Company
backend
Recruiter
backend
Ads
backend
Data
Store
Data
Store
Data
Store
Data
Store
Even if a single server is up 99.999% of the
time, with 1000 servers, the odds that one is
down are
1 - 0.999991000
= ~1%
Here is how to make your async code more
resilient to errors and slow performance
We can use the recover method on a Promise
to specify how to handle errors
public class Errors extends Controller {
public static Result index(String url) {
F.Promise<WS.Response> promise = WS.url(url).get();
return async(promise.map(new F.Function<WS.Response, Result>() {
public Result apply(WS.Response response) throws Throwable {
return ok("Got a response!");
}
}).recover(new F.Function<Throwable, Result>() {
public Result apply(Throwable throwable) throws Throwable {
return internalServerError("Got an exception: " + throwable);
}
}));
}
}
There is an analogous recover method on Scala
Futures as well
object ErrorsScala extends Controller {
def index(url: String) = Action {
Async {
WS.url(url).get().map { response =>
Ok("Got a response: " + response.status)
}.recover { case t: Throwable =>
InternalServerError("Got an exception: " + t)
}
}
}
}
If some of the data you fetch isn't required to
render the page, you can use an Option
pattern
Create a helper method: on success, return
Some<T>. On failure, log the error, return None.
public static <T> Promise<Option<T>> optional(Promise<T> promise){
return promise.map(new Function<T, Option<T>>() {
public Option<T> apply(T value) throws Throwable {
if (value == null) {
return Option.None();
}
return Option.Some(value);
}
}).recover(new Function<Throwable, Option<T>>() {
public Option<T> apply(Throwable t) throws Throwable {
Logger.error("Hit an error", t);
return Option.None();
}
});
}
Wrap Promises with optional and inside of
map, use isDefined to see if you have a value
public static Result index(String url) {
Promise<Option<Response>> promise = optional(WS.url(url).get());
return async(promise.map(new Function<Option<Response>, Result>() {
public Result apply(Option<Response> responseOpt){
if (responseOpt.isDefined()) {
Response response = responseOpt.get();
// Use the response to build the page
} else {
// Build the page without the response
}
}
}));
}
The same pattern is even prettier in Scala, as
Option is a first-class citizen of the language
Reusable helper method to convert Future[T] to
Future[Option[T]]
def optional[T](future: Future[T]): Future[Option[T]] = {
future.map(Option.apply).recover { case t: Throwable =>
Logger.error("Hit an error", t)
None
}
}
Wrap Futures with optional and use pattern
matching, comprehensions, etc within map
object OptionExampleScala extends Controller {
def index(url: String) = Action {
Async {
optional(WS.url(url).get()).map {
case Some(response) => // Use the response to build the page
case _ => // Build the page without the response
}
}
}
}
You can create a Promise that will be redeemed
with someValue after the specified timeout
Promise.timeout(someValue, 500, TimeUnit.MILLISECONDS)
Compose two Promises using or: the result takes
on the value of the first one to complete
Promise<Response> response = WS.url(url).get();
Promise<Either<Object, Response>> withTimeout =
Promise.timeout(null, timeout, TimeUnit.MILLISECONDS).or(response);
If right is defined, you got a value in time;
otherwise, it must have timed out first.
Promise<Response> response = WS.url(url).get();
Promise<Either<Object, Response>> withTimeout =
Promise.timeout(null, timeout, TimeUnit.MILLISECONDS).or(response);
withTimeout.map(new Function<Either<Object, Response>, Result>(){
public Result apply(Either<Object, Response> either) {
if (either.right.isDefined()) {
Response response = either.right.get();
// Use the response to build the page
} else {
// Hit a timeout, build the page without the response
}
}
});
The Scala version
import play.api.libs.concurrent._
object TimeoutExampleScala extends Controller {
def index(url: String, timeout: Long) = Action {
val timeout = Promise.timeout(null, timeout, MILLISECONDS)
val future = timeout.either(WS.url(url).get())
future.map {
case Right(response) => // Use the response to build the page
case _ => // Hit a timeout, build the page without the response
}
}
}
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
It is a collection of async utilities, including...
ParSeq integration for Play to make asynchronous
Java code easier
Task<Response> yahoo = Tasks.wrap(WS.url("http://www.yahoo.com"))
Task<Response> google = Tasks.wrap(WS.url("http://www.google.com"))
Task<Response> bing = Tasks.wrap(WS.url("http://www.bing.com"))
// Create a new Task that will run the tasks above one at a time,
// in the order specified, and complete with the return value of the
// last one
Task<Response> sequential = Tasks.seq(yahoo, google, bing)