Рубрики
Без рубрики

Руководство по отложенному результату весной

Узнайте, как выполнить асинхронную обработку запросов в Spring MVC с помощью DeferredResult.

Автор оригинала: baeldung.

1. Обзор

В этом уроке мы рассмотрим , как мы можем использовать класс DeferredResult в Spring MVC для выполнения асинхронной обработки запросов .

Асинхронная поддержка была введена в Servlet 3.0 и, проще говоря, она позволяет обрабатывать HTTP-запрос в другом потоке, чем поток получателя запроса.

DeferredResult, доступный начиная с Spring 3.2, помогает разгрузить длительные вычисления из рабочего потока http в отдельный поток.

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

Модель обработки асинхронных запросов очень полезна, поскольку она помогает хорошо масштабировать приложение при высоких нагрузках, особенно при интенсивных операциях ввода-вывода.

2. Настройка

Для наших примеров мы будем использовать приложение Spring Boot. Для получения более подробной информации о том, как загрузить приложение, обратитесь к нашей предыдущей статье .

Далее мы продемонстрируем как синхронную, так и асинхронную связь с помощью DeferredResult , а также сравним, как асинхронная связь лучше масштабируется для случаев использования с высокой нагрузкой и интенсивным вводом-выводом.

3. Блокировка службы ОТДЫХА

Давайте начнем с разработки стандартного блокирующего сервиса REST:

@GetMapping("/process-blocking")
public ResponseEntity handleReqSync(Model model) { 
    // ...
    return ResponseEntity.ok("ok");
}

Проблема здесь заключается в том, что поток обработки запроса блокируется до тех пор, пока не будет обработан полный запрос и не будет возвращен результат. В случае длительных вычислений это неоптимальное решение.

Чтобы решить эту проблему, мы можем лучше использовать потоки контейнеров для обработки запросов клиентов, как мы увидим в следующем разделе.

4. Неблокирующий ОТДЫХ С использованием 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 операция на Отложенный результат объект.

Давайте посмотрим на вывод журнала, чтобы убедиться, что наши потоки ведут себя так, как ожидалось:

[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController: 
Received async-deferredresult request
[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController: 
Servlet thread freed
[nio-8080-exec-6] java.lang.Thread : Processing in separate thread

Внутренне поток контейнера получает уведомление, и HTTP-ответ доставляется клиенту. Соединение будет оставаться открытым контейнером(сервлет 3.0 или более поздней версии) до тех пор, пока не поступит ответ или не истечет время ожидания.

5. Отложенные Обратные вызовы

Мы можем зарегистрировать 3 типа обратных вызовов с отложенным результатом: завершение, тайм-аут и обратные вызовы ошибок.

Давайте используем метод onCompletion() для определения блока кода, который выполняется при завершении асинхронного запроса:

deferredResult.onCompletion(() -> LOG.info("Processing complete"));

Аналогично, мы можем использовать on Time out() для регистрации пользовательского кода для вызова при наступлении тайм-аута. Чтобы ограничить время обработки запроса, мы можем передать значение тайм-аута во время создания объекта 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.baeldung.controller.DeferredResultController: 
servlet thread freed
[nio-8080-exec-6] java.lang.Thread: Processing in separate thread
[nio-8080-exec-6] com.baeldung.controller.DeferredResultController: 
Request timeout occurred

Будут сценарии, в которых длительное вычисление завершится неудачей из-за какой-либо ошибки или исключения. В этом случае мы также можем зарегистрировать onError() обратный вызов:

deferredResult.onError((Throwable t) -> {
    deferredResult.setErrorResult(
      ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
        .body("An error occurred."));
});

В случае ошибки при вычислении ответа мы устанавливаем другой статус ответа и тело сообщения с помощью этого обработчика ошибок.

6. Заключение

В этой краткой статье мы рассмотрели, как Spring MVC DeferredResult облегчает создание асинхронных конечных точек.

Как обычно, полный исходный код доступен на Github .