Web• DNS
• Redirect
• Download HTML
• Download CSS + JS + Images
• Download more CSS + JS + Images
• ….
• Connect TCP
• Encryption handshake
• Push request data through socket
• … wait …
• Pull data from the socket
• Assemble and return the result
Time
Network latency: milliseconds
Application instruction: microseconds
Hardware instructions: nanoseconds
So why are we still writing blocking code?
! It works pretty well in monoliths! CPU and memory is cheap! Networks are reliable and fast! Writing non blocking code is hard
28
A Blocking Echo Servlet 32
public class BlockingServlet extends HttpServlet {private static final int BUFFER_SIZE = 1024;
protected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
byte[] buffer = new byte[BUFFER_SIZE];while (true) {
int read = request.getInputStream().read(buffer, 0, BUFFER_SIZE);if (read < 0)
break;response.getOutputStream().write(buffer, 0, read);
}}
}
A Non Blocking Echo Servlet 33
public class NonBlockingServlet extends HttpServlet {private static final int BUFFER_SIZE = 1024;
@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AsyncContext asyncContext = request.startAsync(request, response); asyncContext.setTimeout(0); Echoer echoer = new Echoer(asyncContext); request.getInputStream().setReadListener(echoer); response.getOutputStream().setWriteListener(echoer); } private class Echoer implements ReadListener, WriteListener { private final byte[] buffer = new byte[BUFFER_SIZE]; private final AsyncContext asyncContext; private final ServletInputStream input; private final ServletOutputStream output; private boolean complete;
private Echoer(AsyncContext asyncContext) throws IOException { this.asyncContext = asyncContext; this.input = asyncContext.getRequest().getInputStream(); this.output = asyncContext.getResponse().getOutputStream(); }
@Override public void onDataAvailable() throws IOException { while (input.isReady()) { int read = input.read(buffer); output.write(buffer, 0, read); if (!output.isReady()) return; }
if (input.isFinished()) { complete = true; asyncContext.complete(); } }
@Override public void onAllDataRead() throws IOException {}
@Override public void onWritePossible() throws IOException { if (input.isFinished()) { if (!complete) asyncContext.complete(); } else { onDataAvailable(); } }
@Override public void onError(Throwable failure) { failure.printStackTrace(); } }}
Maybe something simpler 34
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {AsyncContext asyncContext = request.startAsync(request, response);final ServletOutputStream out = resp.getOutputStream();final byte[] buf = new byte[1024];final FileInputStream file = …;out.setWriteListener(new WriteListener() {
@Overridepublic void onWritePossible() throws IOException {
while(out.isReady()) {int len = file.read(buf);if(len<0) {
return;}out.write(buf, 0, len);
}}
});}
Observable<T>
42
Will push zero or more items of type TFollowed by a ‘completed’ message
(or an error…)
RxJava Quick Intro 43
Observable.<String>just("Ouagadougou","Dakar","Accra","Rabat").subscribe(item->System.err.println(item));
OuagadougouDakarAccraRabat
RxJava Quick Intro 44
Observable.range(0, 1000).subscribe(item->System.err.println(item));
0123456…998999
RxJava Quick Intro 45
Observable.range(0, 1000).skip(10).take(10).subscribe(item->System.err.println(item));
10111213141516171819
RxJava Quick Intro 46
Bytes.fromFile("citiesafrica.xml").lift(XML.parse()).subscribe(item->System.err.println(item));
<cities> <africa>
<city name="Lusaka"/> <city name="Harare"/> <city name="Kigali"/>
</africa></cities>
START_DOCUMENTSTART_ELEMENT cities {}START_ELEMENT africa {}START_ELEMENT city {name=Lusaka}END_ELEMENT citySTART_ELEMENT city {name=Harare}END_ELEMENT citySTART_ELEMENT city {name=Kigali}END_ELEMENT cityEND_ELEMENT africaEND_ELEMENT citiesEND_DOCUMENT
RxJava Quick Intro 47
Bytes.fromFile("citiesafrica.xml").lift(XML.parse()).filter(e->e.getType()==XmlEventTypes.START_ELEMENT).map(e->e.getAttributes().get("name")).subscribe(item->System.err.println(item));
<cities> <africa>
<city name="Lusaka"/> <city name="Harare"/> <city name="Kigali"/>
</africa></cities>
LusakaHarareKigali
static Observable<byte[]> createSourceObservable(HttpServletRequest req);static Subscriber<byte[]> createSink(HttpServletResponse resp);
protected void doPost(HttpServletRequest req, final HttpServletResponse resp) {createSourceObservable(req)
.subscribe(createSink(resp));}
RxJava Quick Intro
A Blocking API
public static Double temperatureInCity(String city) {return Double.parseDouble(HTTPClient
.get("http://api.weather.org/weather?q="+city)
.toXml()
.getContent("temperature"));}
For consumers:
Double temperature = temperatureInCity("Tripoli");
Convert to Reactive
public Observable<Double> temperatureInCity(String city) {return Observable.just(
Double.parseDouble(HTTPClient.get("http://api.weather.org/weather?q="+city).toXml().getContent("temperature")
));}
Sort of…
Double temp = temperatureInCity(“Cairo”).blockingFirst();updateTemperatureUI(temp);
This code is still just as blocking
But now the consumer and producer have independent threading
Use a Non Blocking HTTP Client
public static Observable<Double> temperatureInCity(String city) {return HTTP.get("http://api.weather.org/weather?q="+city)
.subscribeOn(Schedulers.io())
.lift(XML.parse())
.filter(e->e.getType()==XmlEventTypes.START_ELEMENT)
.filter(e->e.getText().equals("temperature"))
.first()
.map(xml->Double.parseDouble(xml.getAttributes().get("value")));}
Add a Cache
public Observable<Double> temperatureInCity(String city) {Double temperature = temperatureCache.get(city);if(temperature!=null) {
return Observable.just(temperature);}return HTTP.get("http://api.weather.org/weather?q="+city)
.subscribeOn(Schedulers.io())
.lift(XML.parse())
.filter(e->e.getType()==XmlEventTypes.START_ELEMENT)
.filter(e->e.getText().equals("temperature"))
.first()
.map(xml->Double.parseDouble(xml.getAttributes().get("value")))
.doOnNext(temperature->temperatureCache.put(city,temperature));}
Non Blocking Client
Blocking:
Double temp = temperatureInCity(“Cairo").toBlocking().first();updateTemperatureUI(temp);
Non blocking:
temperatureInCity(“Nairobi”) .observeOn(UISchedulers.uiThread()) .subscribe(d->updateTemperatureUI(d));
A Blocking Echo Servlet 60
public class BlockingServlet extends HttpServlet {private static final int BUFFER_SIZE = 1024;
protected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
byte[] buffer = new byte[BUFFER_SIZE];while (true) {
int read = request.getInputStream().read(buffer, 0, BUFFER_SIZE);if (read < 0)
break;response.getOutputStream().write(buffer, 0, read);
}}
}
RxJava back pressure 62
Bytes.fromNetwork(“something_huge.xml”).lift(XML.parse()).filter(e->e.getType()==XmlEventTypes.START_ELEMENT).map(e->e.getAttributes().get("name")).subscribe(item->doSomethingComplicated(item));
Error handling 67
Servlet.handlePost().lift(XML.parse()).subscribe(item->sendSomeWhere(item));
<cities> <africa>
<city name="Lusaka"/> <city name="Harare"/> <city name="Kigali"/>
</cities>
START_DOCUMENTSTART_ELEMENT cities {}START_ELEMENT africa {}START_ELEMENT city {name=Lusaka}END_ELEMENT citySTART_ELEMENT city {name=Harare}END_ELEMENT citySTART_ELEMENT city {name=Kigali}END_ELEMENT cityEND_ELEMENT africaPARSE_ERROR
Non Blocking is not faster
68
! It is about utilising threads better! If thread utilisation is not an issue, it
will perform similarly
Thoughts69
! RxJava is lightweight and easy to add to existing software
! Pleasant programming model! Makes non blocking I/O bearable! Can be used incrementally
Conclusions70
! Threads are expensive! Everything keeps getting faster except the speed of light! Micro services! Blocking code misbehaves under pressure! The price of blocking communication will keep going up
I believe that, in time, some non blocking code is inevitable