-
Notifications
You must be signed in to change notification settings - Fork 546
Asynchronous Servers and Clients in Rest.li
Rest.li is asynchronous and non-blocking under the hood -
- R2: On the client side R2 uses a Netty based asynchronous client. On the server side if you are using our experimental Netty server is async and non-blocking. If you are using Jetty you need to configure it to run in async mode.
- D2: All communication with ZooKeeper uses the async APIs.
- Rest.li: Rest.li does not handle I/O. All I/O work is done by R2, which is async and non-blocking as explained above. Rest.li uses ParSeq to interact with and delegate to server application code. The
RestClient
used to make Rest.li requests on the client-side has several options in order to write async non-blocking code.
As shown above 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)
There are two main options available to write async server implementations in Rest.li -
- Using
com.linkedin.common.Callback
s (included in Rest.li) - Using ParSeq
Consider the following implementation of a async GET method using Callback
s. In this example we fetch data from ZooKeeper asynchronously, and based on the data that we get back either return a 404
to the user, or build a Greeting
RecordTemplate
. We will use this example to understand how to write Callback
based Rest.li async method implementations -
@RestMethod.Get
public void get(final Long id, @CallbackParam final Callback<Greeting> callback) {
String path = "/data/" + id;
// _zkClient is a regular ZooKeeper client
_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);
}
@RestMethod.Get
public void get(final Long id, @CallbackParam final Callback<Greeting> callback) {
In order to use Callback
s we need to set the return type of the function to be void
and pass in a Callback<T>
as a parameter to the function. T
here is whatever type you would have returned from a synchronous implementation of the same function. In this case the synchronous implementation would have returned a Greeting
, which is why we are returning a Callback<Greeting>
here.
_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);
We use the async ZooKeeper getData
API call to fetch data from ZooKeeper. Based on the data we get back from ZooKeeper in the DataCallback
(which is a ZooKeeper construct) we invoke either the onError
or the onSuccess
method on the Callback
interface.
onError
is used to signify that something went wrong. In this case we invoke onError
with a RestliServiceException
when the length of data that we get back from ZooKeeper is 0. The Rest.li framework translates this Exception
into an appropriate REST response to send back to the client.
In case we get back data that has non-zero length we build a Greeting
object from it in the buildGreeting
method (not shown here) and return that to the client by invoking the onSuccess
method.
In other words, all you have to do is invoke the onError
or onSuccess
method within your method and the Rest.li framework will return data back to the client. This is why the return type for the method is void
, since the Callback
is used to return values back to the client.
It is up to the application developer to execute the Callback
. Rest.li does not execute the Callback
for you. In the above example, the Callback
was executed by the ZooKeeper thread, which is why we didn't have to explicitly execute it.
Consider the following example of an async GET implementation that uses 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);
}
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.
@RestMethod.Get
public Task<Greeting> get(final Long id) {
In order to use ParSeq the function implementation must return either a ParSeq Task<T>
or a Promise<T>
. T
here is whatever type you would have returned from a synchronous implementation of the same function. In this case the synchronous implementation would have returned a Greeting
, which is why we are returning a Task<Greeting>
here.
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);
The basic idea to use ParSeq for Rest.li async method implementations is to return Task
s or Promise
s.
fileDataTask
is a Task
for the FileData
that we read from disk. We want to transform this 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.
So we have two Task
s now, fileDataTask
and mainTask
, with mainTask
depending on the result of fileDataTask
. mainTask
also builds the Greeting
object that we want to return to the client. In order to build this dependency between the two Task
s we use the Tasks.seq
method.
In the above example Task.seq(fileDataTask, mainTask)
returns a new Task
that is executed for you automatically using the ParSeq engine within Rest.li. In other words, you do not have to provide a separate ParSeq execution engine to run this Task
. Rest.li runs the Task
for you and returns an appropriate response to the client.
There are two main options available to make async requests using Rest.li -
- Using
com.linkedin.common.Callback
s - Using a
ParSeqRestClient
Here is a partial example of making a GET request to the /greetings resource and then using the result asynchronously -
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);
Callback<Response<Greeting>> cb = new Callback() {
void onSuccess(Response<Greeting> response) {
// do something with the returned Greeting
}
void onError(Throwable e) {
// whoops
}
}
We need to define the Callback
that will be executed when we get back a Response
from the server. onSuccess
will be invoked by Rest.li on getting a non-exception result (i.e. a Response
), while onError
will be invoked by Rest.li in case an Exception
is thrown. The key concept to note here is that Rest.li invokes the Callback
from you once we get a Response
or an Exception
is thrown.
Request<Greeting> getRequest = BUILDERS.get().id(1L).build();
_restClient.sendRequest(getRequest, new RequestContext(), callback);
We pass the Callback
we defined previously as the last parameter of the sendRequest
call. This calls returns right away, because as stated above, Rest.li invokes the Callback
for you appropriately.
Quick Access:
- Tutorials
- Dynamic Discovery
- Request Response API
-
Rest.li User Guide
- Rest.li Server
- Resources
- Resource Types
- Resource Methods
- Rest.li Client
- Projections
- Filters
- Wire Protocol
- Data Schemas
- Data
- Unstructured Data
- Tools
- FAQ