Руководство по DeferredResult весной
1. обзор
В этом руководстве мы рассмотримhow we can use the DeferredResult class in Spring MVC to perform asynchronous request processing.
Асинхронная поддержка была введена в Servlet 3.0 и, проще говоря, она позволяет обрабатывать HTTP-запрос в другом потоке, а не в потоке получателя запроса.
DeferredResult,, доступный начиная с Spring 3.2 и новее, помогает выгрузить длительные вычисления из потока http-worker в отдельный поток.
Хотя другой поток потребует некоторые ресурсы для вычислений, рабочие потоки тем временем не блокируются и могут обрабатывать входящие клиентские запросы.
Модель обработки асинхронных запросов очень полезна, поскольку она помогает хорошо масштабировать приложение во время высоких нагрузок, особенно для интенсивных операций ввода-вывода.
2. Настроить
В наших примерах мы будем использовать приложение Spring Boot. Для получения дополнительной информации о том, как запустить приложение, обратитесь к нашим предыдущимarticle.
Далее мы продемонстрируем как синхронную, так и асинхронную связь с использованиемDeferredResult and, а также сравним, насколько асинхронный лучше масштабируется для случаев использования с высокой нагрузкой и интенсивным вводом-выводом.
3. Блокировка службы REST
Начнем с разработки стандартной REST-службы блокировки:
@GetMapping("/process-blocking")
public ResponseEntity> handleReqSync(Model model) {
// ...
return ResponseEntity.ok("ok");
}
Проблема здесь в том, что возвращаетсяthe request processing thread is blocked until the complete request is processed и результат. В случае длительных вычислений это неоптимальное решение.
Чтобы решить эту проблему, мы можем лучше использовать потоки контейнеров для обработки клиентских запросов, как мы увидим в следующем разделе.
4. Неблокирующий REST с использованиемDeferredResult
Чтобы избежать блокировки, мы будем использовать модель программирования на основе обратных вызовов, в которой вместо фактического результата мы вернемDeferredResult в контейнер сервлета.
@GetMapping("/async-deferredresult")
public DeferredResult> handleReqDefResult(Model model) {
LOG.info("Received async-deferredresult request");
DeferredResult> output = new DeferredResult<>();
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
}
output.setResult(ResponseEntity.ok("ok"));
});
LOG.info("servlet thread freed");
return output;
}
Обработка запроса выполняется в отдельном потоке, и по завершении мы вызываем операциюsetResult для объектаDeferredResult.
Давайте посмотрим на вывод журнала, чтобы убедиться, что наши потоки ведут себя должным образом:
[nio-8080-exec-6] com.example.controller.AsyncDeferredResultController:
Received async-deferredresult request
[nio-8080-exec-6] com.example.controller.AsyncDeferredResultController:
Servlet thread freed
[nio-8080-exec-6] java.lang.Thread : Processing in separate thread
Внутри контейнерный поток уведомляется, и HTTP-ответ доставляется клиенту. Контейнер будет оставаться открытым (сервлет 3.0 или новее) до тех пор, пока ответ не придет или не истечет время ожидания.
5. DeferredResult Обратные вызовы
Мы можем зарегистрировать 3 типа обратных вызовов с помощью DeferredResult: завершение, тайм-аут и обратные вызовы при ошибках.
Давайте воспользуемся методомonCompletion(), чтобы определить блок кода, который выполняется после завершения асинхронного запроса:
deferredResult.onCompletion(() -> LOG.info("Processing complete"));
Точно так же мы можем использоватьonTimeout() для регистрации пользовательского кода, который будет запускаться при истечении времени ожидания. Чтобы ограничить время обработки запроса, мы можем передать значение тайм-аута во время создания объектаDeferredResult:
DeferredResult> deferredResult = new DeferredResult<>(500l);
deferredResult.onTimeout(() ->
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
.body("Request timeout occurred.")));
В случае тайм-аутов мы устанавливаем другой статус ответа с помощью обработчика тайм-аута, зарегистрированного вDeferredResult.
Давайте вызовем ошибку тайм-аута, обработав запрос, который занимает больше заданных значений тайм-аута в 5 секунд:
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
...
}
deferredResult.setResult(ResponseEntity.ok("OK")));
});
Посмотрим на журналы:
[nio-8080-exec-6] com.example.controller.DeferredResultController:
servlet thread freed
[nio-8080-exec-6] java.lang.Thread: Processing in separate thread
[nio-8080-exec-6] com.example.controller.DeferredResultController:
Request timeout occurred
Будут сценарии, в которых длительные вычисления не будут выполнены из-за какой-либо ошибки или исключения. В этом случае мы также можем зарегистрировать обратный вызовonError():
deferredResult.onError((Throwable t) -> {
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An error occurred."));
});
В случае ошибки при вычислении ответа мы устанавливаем другой статус ответа и тело сообщения с помощью этого обработчика ошибок.
6. Заключение
В этой быстрой статье мы рассмотрели, как Spring MVCDeferredResult упрощает создание асинхронных конечных точек.
Как обычно, доступен полный исходный кодover on Github.