Как создать неблокирующий веб-сервис @RestController весной? - PullRequest
3 голосов
/ 09 мая 2019

У меня есть @RestController метод веб-сервиса, который может блокировать поток ответов длительным вызовом службы. Следующим образом:

@RestController
public class MyRestController {
    //could be another webservice api call, a long running database query, whatever
    @Autowired
    private SomeSlowService service;

    @GetMapping()
    public Response get() {
        return service.slow();
    }

    @PostMapping()
    public Response get() {
        return service.slow();
    }
}

Проблема: что, если пользователи X звонят мне сюда? Все выполняющиеся потоки будут блокироваться до тех пор, пока не будет получен ответ. Таким образом съедая "макс-соединения", макс темы и т. Д.

Я помню, как некоторое время назад читал статью о том, как решить эту проблему, как-то парковать потоки, пока не будет получен медленный ответ службы. Так что эти потоки не будут блокировать, например, tomcat max соединение / пул.

Но я больше не могу его найти. Может кто знает как это решить?

Ответы [ 2 ]

2 голосов
/ 09 мая 2019

Есть несколько решений, например, работа с асинхронными запросами . В этих случаях поток снова станет свободным, как только будут возвращены (и не обязательно завершены) CompletableFuture, DeferredResult, Callable, ....


Например, допустим, мы настраиваем Tomcat следующим образом:

server.tomcat.max-threads=5 # Default = 200

И у нас есть следующий контроллер:

@GetMapping("/bar")
public CompletableFuture<String> getSlowBar() {
    return CompletableFuture.supplyAsync(() -> {
        silentSleep(10000L);
        return "Bar";
    });
}

@GetMapping("/baz")
public String getSlowBaz() {
    logger.info("Baz");
    silentSleep(10000L);
    return "Baz";
}

Если бы мы запустили 100 запросов одновременно, вам пришлось бы подождать не менее 200 секунд, прежде чем будут обработаны все вызовы getSlowBar(), поскольку только 5 могут обрабатываться в данный момент времени. С другой стороны, с асинхронным запросом вам придется подождать не менее 10 секунд, поскольку все запросы, скорее всего, будут обработаны одновременно, и тогда поток станет доступен для использования другими.

Есть ли разница между CompletableFuture, Callable и DeferredResult? Нет никакой разницы в отношении результатов, они все ведут себя одинаково.

Способ обработки потоков несколько отличается:

  • С Callable вы полагаетесь на то, что Spring выполнит Callable с помощью TaskExecutor
  • С DeferredResult вы должны обрабатывать нити самостоятельно. Например, выполняя логику в ForkJoinPool.commonPool().
  • С CompletableFuture вы можете либо полагаться на пул потоков по умолчанию (ForkJoinPool.commonPool()), либо указывать свой собственный пул потоков.

Кроме того, CompletableFuture и Callable являются частью спецификации Java, а DeferredResult является частью среды Spring.


Имейте в виду, что хотя потоки освобождаются, соединения все еще остаются открытыми для клиента. Это означает, что при обоих подходах максимальное количество запросов, которые могут быть обработаны одновременно, ограничено 10000 и может быть настроено с помощью:

server.tomcat.max-connections=100 # Default = 10000
0 голосов
/ 10 мая 2019

по моему мнению. Асинхронность может быть лучше для сервера. Для данного конкретного API асинхронность не работает хорошо. Клиенты также удерживают соединения.в конце концов он будет поглощать «max-connections». Вы можете отправить запрос в messagequeue (kafka) и вернуть успех клиентам.затем вы получаете запрос и передаете его медленному сервису.

...