Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Long Polling in Spring

Tags:

java

spring

We have a somewhat unique case where we need to interface with an outside API that requires us to long poll their endpoint for what they call real time events.

The thing is we may have as many as 80,000 people/devices hitting this endpoint at any given time, listening for events, 1 connection per device/person.

When a client makes a request from our Spring service to long poll for events, our service then in turn makes an async call to the outside API to long poll for events. The outside API has defined the minimum long poll timeout may be set to 180 seconds.

So here we have a situation where a thread pool with a queue will not work, because if we have a thread pool with something like (5 min, 10 max, 10 queue) then the 10 threads getting worked on may hog the spotlight and the 10 in queue will not get a chance until one of the current 10 are done.

We need a serve it or fail it (we will put load balancers etc. behind it), but we don't want to leave a client hanging without actual polling happening.

We have been looking into using DeferredResult for this, and returning that from the controller.

Something to the tune of

@RequestMapping(value = "test/deferredResult", method = RequestMethod.GET)
    DeferredResult<ResponseEntity> testDeferredResult() {
        final DeferredResult<ResponseEntity> deferredResult = new DeferredResult<ResponseEntity>();
        CompletableFuture.supplyAsync(() -> testService.test()).whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
        return deferredResult;
    }

I am questioning if I am on the right path, and also should I provide an executor and what kind of executor (and configuration) to the CompletableFuture.supplyAsync() method to best accomplish our task.

I have read various articles, posts, and such and am wanting to see if anyone has any knowledge that might help our specific situation.

like image 502
WIllJBD Avatar asked Jan 26 '17 16:01

WIllJBD


1 Answers

The problem you are describing does not sound like one that can be solved nicely if you are using blocking IO. So you are on the right path, because DeferredResult allows you to produce the result using any thread, without blocking the servlet-container thread.

With regards to calling a long-pooling API upstream, you need a NIO solution as well. If you use a Netty client, you can manage several thousand sockets using a single thread. When the NIO selector in Netty detects data, you will get a channel callback and eventually delegate to a thread in the Netty worker thread pool, and you can call deferredResult.setResult. If you don't do blocking IO the worker pool is usually sized after the number of CPU-cores, otherwise you may need more threads.

There are still a number of challenges.

  • You probably need more than one server (or network interface) since there are only 65K ports.
  • Sockets in Java does not have write timeouts, so if a client refuses to read data from the socket, and you send more data than your socket buffer, you would block the Netty worker thread(s) and then everything would stop (reverse slow loris attack). This is a classic problem in large async setups, and one of the reasons for using frameworks like Hystrix (by Netflix).
like image 157
Klaus Groenbaek Avatar answered Oct 11 '22 23:10

Klaus Groenbaek