SlideShare a Scribd company logo
Async servers and clients in 
Rest.li 
1
(Please read the speaker notes) 
2
What is this Rest.li thing 
you speak about? 
“Rest.li is an open source REST framework for building robust, 
scalable RESTful architectures using type-safe bindings and 
asynchronous, non-blocking I/O.” 
3
The Stack 
Rest.li Data and REST API 
D2 Dynamic Discovery and load balancing 
R2 Network communication 
4
What do you by mean 
by async? 
5
What do you mean by 
async (I/O)? 
6 
Request 1 
Request 2 
Blocked on I/O 
Time 
Sync Async 
Free CPU
Rest.li is async and non-blocking 
under the hood! 
7
Rest.li 
D2 
R2 
8
How is R2 async and 
non-blocking? 
• Netty based async client 
• Jetty based server – configuration change (link) needed to run 
the server in async mode 
Rest.li 
D2 
R2 
9
How is D2 async and 
non-blocking? 
• All communication with ZooKeeper uses the asynchronous 
APIs 
• ZooKeeper pushes data to clients in case of updates 
Rest.li 
D2 
R2 
10
How is Rest.li async 
and non-blocking? 
• Does not handle I/O 
• I/O is taken care of by R2 
• R2 is async and non-blocking! 
• Uses ParSeq when interacting with and delegating to 
application code. 
Rest.li 
D2 
R2 
11
Async Rest.li Servers 
12
Async Server 
Implementations 
• The Rest.li framework is async and non-blocking under the 
hood. 
• As a result of this, if you do any blocking work in your method 
implementation it can negatively impact your application 
throughput as threads are held up by your application which 
are needed by Rest.li. (if you are using Rest.li in async 
mode) 
13
Async Server 
Implementations - 
Options 
1. Callback based 
2. ParSeq based 
14
Async Server – Callback 
@RestMethod.Get 
public void get(final Long id, @CallbackParam final Callback<Greeting> 
callback) { 
String path = "/data/" + id; 
_zkClient.getData(path, false, new DataCallback() { 
public void processResult(int i, String s, Object o, byte[] b, Stat 
st) { 
if (b.length == 0) { 
callback.onError(new 
RestLiServiceException(HttpStatus.S_404_NOT_FOUND)); 
} 
else { 
callback.onSuccess(buildGreeting(b)); 
} 
} 
}, null); 
} 15
Async Server – 
Callback Signature 
@RestMethod.Get 
public void get(final Long id, @CallbackParam 
final Callback<Greeting> callback) 
com.linkedin.common.Callback 
16
Async Server – Filling 
out the callback 
_zkClient.getData(path, false, new DataCallback() { 
public void processResult(int i, String s, Object o, 
byte[] b, Stat st) { 
if (b.length == 0) { 
callback.onError(new 
RestLiServiceException(HttpStatus.S_404_NOT_FOUND)); 
} 
else { 
callback.onSuccess(buildGreeting(b)); 
} 
} 
}, null); 17
Async Server – Callback 
@RestMethod.Get 
public void get(final Long id, @CallbackParam final Callback<Greeting> 
callback) { 
String path = "/data/" + id; 
_zkClient.getData(path, false, new DataCallback() { 
public void processResult(int i, String s, Object o, byte[] b, Stat 
st) { 
if (b.length == 0) { 
callback.onError(new 
RestLiServiceException(HttpStatus.S_404_NOT_FOUND)); 
} 
else { 
callback.onSuccess(buildGreeting(b)); 
} 
} 
}, null); 
} 18
Async server - ParSeq 
19
Key ParSeq Concepts 
• Promise Fully async Java Future + listener mechanism. 
• Task Basic unit of work that is similar to Java Callable. 
Task<T> 
• Engine runs Tasks. 
• Context Used by a Task to run sub-Tasks. 
• Composition par and seq 
(link) 
20
Async Server – ParSeq 
@RestMethod.Get 
public Task<Greeting> get(final Long id) { 
final Task<FileData> fileDataTask = buildFileDataTask(); 
final Task<Greeting> mainTask = Tasks.callable("main", new 
Callable<Greeting>() { 
@Override 
public Greeting call() throws Exception { 
FileData fileData = fileDataTask.get(); 
return buildGreetingFromFileData(id, fileData); 
} 
}); 
return Tasks.seq(fileDataTask, mainTask); 
} 
21
Async Server – ParSeq 
signature 
@RestMethod.Get 
public Task<Greeting> get(final Long id) 
22
Async Server – ParSeq body 
final Task<FileData> fileDataTask = buildFileDataTask(); 
23
Async Server – ParSeq body 
final Task<Greeting> mainTask = Tasks.callable("main", new 
Callable<Greeting>() { 
@Override 
public Greeting call() throws Exception { 
FileData fileData = fileDataTask.get(); 
return buildGreetingFromFileData(id, fileData); 
} 
}); 
24
Async Server – 
assembling the Tasks 
return Tasks.seq(fileDataTask, mainTask); 
25 
fileDataTask mainTask
Async Server – ParSeq 
@RestMethod.Get 
public Task<Greeting> get(final Long id) { 
final Task<FileData> fileDataTask = buildFileDataTask(); 
final Task<Greeting> mainTask = Tasks.callable("main", new 
Callable<Greeting>() { 
@Override 
public Greeting call() throws Exception { 
FileData fileData = fileDataTask.get(); 
return buildGreetingFromFileData(id, fileData); 
} 
}); 
return Tasks.seq(fileDataTask, mainTask); 
} 
26
Async Server – ParSeq 
• Can use this approach to build complex call graphs and 
return the final Task. (more on this later). 
• Can use ParSeq tracing 
• No callback hell! 
27
Async Rest.li Clients 
28
Async Rest.li requests 
• RestClient is async and non-blocking under the hood – it 
uses ParSeq and Netty. 
• Applications can still block the thread if they block on the 
results of the Rest.li call! 
Response response = 
_restClient.sendRequest(BUILDER.get().id(1L).build()).get(); 
Fortune fortune = response.getEntity(); 
Blocking on the result of a Future 
29
Async Rest.li requests 
Two Options: 
1. Callback based API 
2. Use a ParSeqRestClient – This is a wrapper around a 
RestClient that returns ParSeq Tasks and Promises. 
30
Async Rest.li requests 
– Callbacks 
Callback<Response<Greeting>> cb = new Callback() { 
void onSuccess(Response<Greeting> response) { 
// do something with the returned Greeting 
} 
void onError(Throwable e) { 
// whoops 
} 
} 
Request<Greeting> getRequest = BUILDERS.get().id(1L).build(); 
_restClient.sendRequest(getRequest, new RequestContext(), cb); 
31
Async Rest.li requests – defining 
a Callback 
Callback<Response<Greeting>> callback = new 
Callback() { 
void onSuccess(Response<Greeting> response) { 
// do something with the returned Greeting 
} 
void onError(Throwable e) { 
// whoops 
} 
} 
32
Async Rest.li requests – sending 
the request 
Request<Greeting> getRequest = 
BUILDERS.get().id(1L).build(); 
_restClient.sendRequest(getRequest, 
new RequestContext(), 
callback); 
33
Async Rest.li requests – using 
ParSeq 
• Callback based API is good for simple use cases (one or two 
requests and you are done.) 
• But it is hard to express more complicated call flows using the 
Callback API 
Callback ParSeq 
34
Async Rest.li requests – using 
ParSeq 
a1 a2 a3 
b1 b2 
c1 
35
Async Rest.li requests – using 
ParSeq 
Task<Response<?>> a1ResponseTask = 
_parseqRestClient.createTask(a1Request); 
Task<Response<?>> a2ResponseTask = 
_parseqRestClient.createTask(a2Request); 
Task<Response<?>> a3ResponseTask = 
_parseqRestClient.createTask(a3Request); a1 a2 a3 
b1 b2 
c1 
36
Async Rest.li requests – using 
ParSeq 
Task<Response<?>> b1ResponseTask = Tasks.callable("", new 
Callable<Task<Response<?>>() { 
@Override 
public Task<Response<?>> call() throws Exception { 
Response<?> a1Response = a1ResponseTask.get(); 
// Similarly, access the results from a2 and a3 as well 
// use this to build request b1Request 
return _parseqRestClient.createTask(b1Request) 
} 
}); 
Task<Response<?>> b2ResponseTask = 
_parseqRestClient.createTask(b2Request); 
a1 a2 a3 
b1 b2 
c1 
37
Async Rest.li requests – using 
ParSeq 
Task<Response<?>> c1ResponseTask = Tasks.callable("", new 
Callable<Task<Response<?>>() { 
@Override 
public Task<Response<?>> call() throws Exception { 
Response<?> b1Response = 
b1ResponseTask.get(); 
Response<?> b2Response = 
b2ResponseTask.get(); 
// use b1Response and b2Response for 
// c1Request 
return _parseqRestClient.createTask(c1Request) 
} 
}); 
a1 a2 a3 
b1 b2 
c1 
38
Async Rest.li requests – using 
ParSeq 
_engine.run( 
Tasks.seq( 
Tasks.par(a1ResponseTask, a2ResponseTask, a3ResponseTask), 
Tasks.par(b1ResponseTask, b2ResponseTask), 
c1ResponseTask)); 
a1 a2 a3 
b1 b2 
c1 
39
Conclusion 
• Rest.li is async and non-blocking under the hood 
• You can use Callbacks and ParSeq Tasks and Promises to 
write async Rest.li clients and servers 
40
Links and references 
• Rest.li user guide https://github.com/linkedin/rest.li/wiki/Rest.li- 
User-Guide 
• Asynchronous servers and clients in Rest.li 
https://github.com/linkedin/rest.li/wiki/Asynchronous-Servers-and- 
Clients-in-Rest.li 
• ParSeq https://github.com/linkedin/parseq 
41
Me. 
https://www.linkedin.com/in/parikhkaran 
https://github.com/karanparikh 
http://karanparikh.com/ 
42
Thanks! 
43

More Related Content

Async servers and clients in Rest.li

  • 1. Async servers and clients in Rest.li 1
  • 2. (Please read the speaker notes) 2
  • 3. What is this Rest.li thing you speak about? “Rest.li is an open source REST framework for building robust, scalable RESTful architectures using type-safe bindings and asynchronous, non-blocking I/O.” 3
  • 4. The Stack Rest.li Data and REST API D2 Dynamic Discovery and load balancing R2 Network communication 4
  • 5. What do you by mean by async? 5
  • 6. What do you mean by async (I/O)? 6 Request 1 Request 2 Blocked on I/O Time Sync Async Free CPU
  • 7. Rest.li is async and non-blocking under the hood! 7
  • 9. How is R2 async and non-blocking? • Netty based async client • Jetty based server – configuration change (link) needed to run the server in async mode Rest.li D2 R2 9
  • 10. How is D2 async and non-blocking? • All communication with ZooKeeper uses the asynchronous APIs • ZooKeeper pushes data to clients in case of updates Rest.li D2 R2 10
  • 11. How is Rest.li async and non-blocking? • Does not handle I/O • I/O is taken care of by R2 • R2 is async and non-blocking! • Uses ParSeq when interacting with and delegating to application code. Rest.li D2 R2 11
  • 13. Async Server Implementations • The Rest.li framework is async and non-blocking under the hood. • As a result of this, if you do any blocking work in your method implementation it can negatively impact your application throughput as threads are held up by your application which are needed by Rest.li. (if you are using Rest.li in async mode) 13
  • 14. Async Server Implementations - Options 1. Callback based 2. ParSeq based 14
  • 15. Async Server – Callback @RestMethod.Get public void get(final Long id, @CallbackParam final Callback<Greeting> callback) { String path = "/data/" + id; _zkClient.getData(path, false, new DataCallback() { public void processResult(int i, String s, Object o, byte[] b, Stat st) { if (b.length == 0) { callback.onError(new RestLiServiceException(HttpStatus.S_404_NOT_FOUND)); } else { callback.onSuccess(buildGreeting(b)); } } }, null); } 15
  • 16. Async Server – Callback Signature @RestMethod.Get public void get(final Long id, @CallbackParam final Callback<Greeting> callback) com.linkedin.common.Callback 16
  • 17. Async Server – Filling out the callback _zkClient.getData(path, false, new DataCallback() { public void processResult(int i, String s, Object o, byte[] b, Stat st) { if (b.length == 0) { callback.onError(new RestLiServiceException(HttpStatus.S_404_NOT_FOUND)); } else { callback.onSuccess(buildGreeting(b)); } } }, null); 17
  • 18. Async Server – Callback @RestMethod.Get public void get(final Long id, @CallbackParam final Callback<Greeting> callback) { String path = "/data/" + id; _zkClient.getData(path, false, new DataCallback() { public void processResult(int i, String s, Object o, byte[] b, Stat st) { if (b.length == 0) { callback.onError(new RestLiServiceException(HttpStatus.S_404_NOT_FOUND)); } else { callback.onSuccess(buildGreeting(b)); } } }, null); } 18
  • 19. Async server - ParSeq 19
  • 20. Key ParSeq Concepts • Promise Fully async Java Future + listener mechanism. • Task Basic unit of work that is similar to Java Callable. Task<T> • Engine runs Tasks. • Context Used by a Task to run sub-Tasks. • Composition par and seq (link) 20
  • 21. Async Server – ParSeq @RestMethod.Get public Task<Greeting> get(final Long id) { final Task<FileData> fileDataTask = buildFileDataTask(); final Task<Greeting> mainTask = Tasks.callable("main", new Callable<Greeting>() { @Override public Greeting call() throws Exception { FileData fileData = fileDataTask.get(); return buildGreetingFromFileData(id, fileData); } }); return Tasks.seq(fileDataTask, mainTask); } 21
  • 22. Async Server – ParSeq signature @RestMethod.Get public Task<Greeting> get(final Long id) 22
  • 23. Async Server – ParSeq body final Task<FileData> fileDataTask = buildFileDataTask(); 23
  • 24. Async Server – ParSeq body final Task<Greeting> mainTask = Tasks.callable("main", new Callable<Greeting>() { @Override public Greeting call() throws Exception { FileData fileData = fileDataTask.get(); return buildGreetingFromFileData(id, fileData); } }); 24
  • 25. Async Server – assembling the Tasks return Tasks.seq(fileDataTask, mainTask); 25 fileDataTask mainTask
  • 26. Async Server – ParSeq @RestMethod.Get public Task<Greeting> get(final Long id) { final Task<FileData> fileDataTask = buildFileDataTask(); final Task<Greeting> mainTask = Tasks.callable("main", new Callable<Greeting>() { @Override public Greeting call() throws Exception { FileData fileData = fileDataTask.get(); return buildGreetingFromFileData(id, fileData); } }); return Tasks.seq(fileDataTask, mainTask); } 26
  • 27. Async Server – ParSeq • Can use this approach to build complex call graphs and return the final Task. (more on this later). • Can use ParSeq tracing • No callback hell! 27
  • 29. Async Rest.li requests • RestClient is async and non-blocking under the hood – it uses ParSeq and Netty. • Applications can still block the thread if they block on the results of the Rest.li call! Response response = _restClient.sendRequest(BUILDER.get().id(1L).build()).get(); Fortune fortune = response.getEntity(); Blocking on the result of a Future 29
  • 30. Async Rest.li requests Two Options: 1. Callback based API 2. Use a ParSeqRestClient – This is a wrapper around a RestClient that returns ParSeq Tasks and Promises. 30
  • 31. Async Rest.li requests – Callbacks Callback<Response<Greeting>> cb = new Callback() { void onSuccess(Response<Greeting> response) { // do something with the returned Greeting } void onError(Throwable e) { // whoops } } Request<Greeting> getRequest = BUILDERS.get().id(1L).build(); _restClient.sendRequest(getRequest, new RequestContext(), cb); 31
  • 32. Async Rest.li requests – defining a Callback Callback<Response<Greeting>> callback = new Callback() { void onSuccess(Response<Greeting> response) { // do something with the returned Greeting } void onError(Throwable e) { // whoops } } 32
  • 33. Async Rest.li requests – sending the request Request<Greeting> getRequest = BUILDERS.get().id(1L).build(); _restClient.sendRequest(getRequest, new RequestContext(), callback); 33
  • 34. Async Rest.li requests – using ParSeq • Callback based API is good for simple use cases (one or two requests and you are done.) • But it is hard to express more complicated call flows using the Callback API Callback ParSeq 34
  • 35. Async Rest.li requests – using ParSeq a1 a2 a3 b1 b2 c1 35
  • 36. Async Rest.li requests – using ParSeq Task<Response<?>> a1ResponseTask = _parseqRestClient.createTask(a1Request); Task<Response<?>> a2ResponseTask = _parseqRestClient.createTask(a2Request); Task<Response<?>> a3ResponseTask = _parseqRestClient.createTask(a3Request); a1 a2 a3 b1 b2 c1 36
  • 37. Async Rest.li requests – using ParSeq Task<Response<?>> b1ResponseTask = Tasks.callable("", new Callable<Task<Response<?>>() { @Override public Task<Response<?>> call() throws Exception { Response<?> a1Response = a1ResponseTask.get(); // Similarly, access the results from a2 and a3 as well // use this to build request b1Request return _parseqRestClient.createTask(b1Request) } }); Task<Response<?>> b2ResponseTask = _parseqRestClient.createTask(b2Request); a1 a2 a3 b1 b2 c1 37
  • 38. Async Rest.li requests – using ParSeq Task<Response<?>> c1ResponseTask = Tasks.callable("", new Callable<Task<Response<?>>() { @Override public Task<Response<?>> call() throws Exception { Response<?> b1Response = b1ResponseTask.get(); Response<?> b2Response = b2ResponseTask.get(); // use b1Response and b2Response for // c1Request return _parseqRestClient.createTask(c1Request) } }); a1 a2 a3 b1 b2 c1 38
  • 39. Async Rest.li requests – using ParSeq _engine.run( Tasks.seq( Tasks.par(a1ResponseTask, a2ResponseTask, a3ResponseTask), Tasks.par(b1ResponseTask, b2ResponseTask), c1ResponseTask)); a1 a2 a3 b1 b2 c1 39
  • 40. Conclusion • Rest.li is async and non-blocking under the hood • You can use Callbacks and ParSeq Tasks and Promises to write async Rest.li clients and servers 40
  • 41. Links and references • Rest.li user guide https://github.com/linkedin/rest.li/wiki/Rest.li- User-Guide • Asynchronous servers and clients in Rest.li https://github.com/linkedin/rest.li/wiki/Asynchronous-Servers-and- Clients-in-Rest.li • ParSeq https://github.com/linkedin/parseq 41

Editor's Notes

  1. R2 – RESTful abstraction built on Netty and Jetty. D2 – Dynamic discovery and client side load balancing using ZooKeeper. Rest.li – Data layer + layer that exposes RESTful API that application can use to build RESTful services.
  2. Async I/O basically means that we free up our thread of computation to do other things while we are waiting for the I/O to complete. In other words we make an I/O request and we don’t block waiting on the result of the I/O to appear. Let’s look at one thread on the server. Assume both request 1 and request 2 arrive at the same time. In each request the thread does some work, then makes an I/O request of some sort, and then uses the values from the I/O response to fulfill the request. In the synchronous model we can’t start processing request 2 until request 1 is done because the thread is blocked on I/O. In the gray portions our server is essentially idle. In the async model we can start processing request 2 instead of blocking on the I/O to complete.
  3. For the rest of the talk we will assume we are running the R2 server in async mode.
  4. Jetty configuration is needed to run Rest.li in async mode. If you are running Rest.li in a thread-per-request model async programming will not make much of a difference since the thread cannot be re-used to serve new requests.
  5. ParSeq is a framework that makes it easier to write asynchronous code in Java. Created by LinkedIn and is an open source project.
  6. In this example we are fetching data from ZooKeeper using the async ZooKeeper API and then converting that data into a Greeting object to return.
  7. Need to explicitly specify the callback in the method signature. Notice the void return type. The next slide will explain how a response is sent back to the client.
  8. The callback API exposes two methods – onSuccess and onError. In this example we invoke the onSuccess method if the data we get from ZooKeeper has a length > 0. We invoke the onError method otherwise. Once either onError or onSucess has been invoked the framework will return a response back to the client.
  9. ParSeq is a library that makes it easy to write complex async task flows in Java. It was created by LinkedIn and is an open source project.
  10. Difference between Task and Callable is that in a Task you can set the result asynchronously.
  11. In this example we read data from the disk asynchronously and use that to build a Greeting object. buildFileDataTask (implementation not shown here) reads some file on disk using async I/O and returns a Task<FileData>, where FileData (implementation not shown here) is some abstraction for the data being read. We use FileData to build a Greeting to return to the client.
  12. Return ParSeq tasks from methods. These are run by Rest.li using the internal ParSeq engine. Task<T>  if you run this Task using a ParSeq engine it will return you a T eventually.
  13. We first read a file on disk using some async I/O abstraction that gives us back a Task for the data read.
  14. The basic idea to use ParSeq for Rest.li async method implementations is to return Tasks or Promises. We want to transform the FileData into a Greeting to return to the user. We define a new Task, called mainTask in the example above, to do this. Within the call method of mainTask we obtain the FileData from the fileDataTask. This is a non-blocking call because of the way we assemble our final call graph (more on this in a bit). Finally, we build a Greeting in the buildGreetingFromFileData (implementation not shown here) method.
  15. This is what will be returned from the overall method. Tasks.seq is a way to build a call graph between Tasks in ParSeq. In this example we are saying that we want mainTask to run after fileDataTask. This is because mainTask depends on fileDataTask. Once we express our call graph dependency in this way, when we call fileDataTask.get() within the mainTask it will either return right away or throw an exception. This is because ParSeq will make sure that mainTask only gets run AFTER fileDataTask has completed. Tasks.seq() returns a new Task and this Task will get run by the underlying ParSeq engine within Rest.li.
  16. This call is blocking because sendRequest returns a Future, and calling get() on the Future waits until the operation has been completed.
  17. This is very similar to callback based approached present in Node.js and JavaScript.
  18. The first thing we need to do is define the callback that will be executed when the server sends us a response, or there is an error.
  19. Then we use the Callback based sendRequest API to send the request. Rest.li invokes the callback for you upon getting a result.
  20. Each circle is a response from making a Rest.li request. Each layer represents requests being made in parallel. The yellow circle Is the final result.
  21. b1 and b2 don’t depend on each other so theoretically b2 could have been run in the same level as a1, a2, or a3.
  22. Use the parseq rest client to create three response tasks.
  23. Combine the results from a1-a3 and use that to make another request task b1
  24. Similarly combine the results of b1 and b2 and create another request task c1
  25. Use the Tasks.seq() and Tasks.par() methods to build the call graph. Within each level we want each Task to run in parallel, which is why we use Tasks.par(a1ResponseTask, a2ResponseTask, a3ResponseTask) and Tasks.par(b1ResponseTask, b2ResponseTask). Finally, we want each level to run sequentially one after the other, which is why wrap everything in a Tasks.seq.