Non Blocking I/O for Everyone with RxJava

71

Transcript of Non Blocking I/O for Everyone with RxJava

8 - 24 minute delay

± 30 min. round trip

Web• DNS

• Redirect

• Download HTML

• Download CSS + JS + Images

• Download more CSS + JS + Images

• ….

Ordering a pizza

• Several hours per page

• About 8 pages per order

Thread.setName(“Bob”);

String token = getGitHubToken("https://github.com/login",<params>);

• Connect TCP

• Encryption handshake

• Push request data through socket

• … wait …

• Pull data from the socket

• Assemble and return the result

Blink of an eye100ms

Time

Network latency: milliseconds

Application instruction: microseconds

Hardware instructions: nanoseconds

String token = getGitHubToken("https://github.com/login",<params>);

=

#WRONG

Non Blocking For Everyone with RxJava

@lyaruu

Frank Lyaruu CTO Dexels

Non Blocking in Action 27

https://webtide.com/async-rest

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

Writing non blocking code is rough

29

Servlet API

30

Non Blocking I/O Servlet API 3.1

31

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);

}}

});}

35Bob

36Developer

Node.js 37

var server = http.createServer(function (req, res) { req.pipe(res);});

Node.js 38

var server = http.createServer(function (req, res) { req.pipe(res);});

A pipe… 39

Reactive Programming

40

Programming pipes

ReactiveX41

Polyglot

RxJava RxScala RxJS RxPHP

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

Reactive API’s

49

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));

Challenges57

Back pressure58

Back pressure59

Operation

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);

}}

}

Back pressure61

! Observable: No back pressure! Flowable: Back pressure

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));

Back pressure63

Operation

Back pressure64

Operation

Memory Consumption65

! Much better! … or much worse

Error Handling66

! Some patterns don’t work any more! Requires some thought

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

Frank @Lyaruu dexels.comDexels [email protected]

Please rate!