Получить доступ

Reactive и Spring WebFlux

Неблокирующая модель и event loop, Reactive Streams и Reactor (Mono/Flux), backpressure, реактивный стек целиком, выбор MVC/WebFlux и цена сложности.

Зачем неблокирующая модель

В модели thread-per-request (MVC на Tomcat) поток держится всё время запроса, включая блокирующие ожидания IO, поэтому число одновременных запросов ограничено размером пула (см. урок про сервлеты).

Реактивная модель строится на event loop: небольшое число потоков (примерно по числу ядер) обслуживает тысячи соединений. Поток не блокируется на IO — он инициирует операцию и переключается на другую работу, а продолжение выполняется по готовности. Цель — высокая конкуренция на IO при малом числе потоков и памяти.

Reactive Streams и Reactor

В основе — стандарт Reactive Streams (Publisher, Subscriber, Subscription, Processor) с асинхронным обменом и backpressure. В Spring его реализует Project Reactor, дающий два типа Publisher: Mono (0..1 значение) и Flux (0..N значений).

Ключевое свойство — ленивость: пока на поток не подписались (subscribe), не происходит НИЧЕГО. В WebFlux подписывается сам фреймворк, когда отдаёт ответ; вызывать subscribe/block в коде контроллера обычно не нужно и вредно.

Операторы и пайплайн

Поток обрабатывают декларативно операторами: map (преобразовать), flatMap (вложенная асинхронная операция, возвращающая свой Mono/Flux), filter, zip (объединить), onErrorResume (обработка ошибок). Они лишь СТРОЯТ конвейер — он выполнится при подписке.

Мышление здесь иное, чем в императивном коде: вы описываете трансформации потока, а не последовательность шагов.

Mono<UserDto> result = userClient.getUser(id) // неблокирующий вызов
    .map(UserDto::from)
    .onErrorResume(NotFoundException.class, e -> Mono.empty());

Backpressure

Главное отличие от «просто async» — backpressure: subscriber через request(n) сообщает, сколько элементов готов принять, и быстрый продюсер не зальёт его данными сверх возможностей. Это встроенный механизм защиты от перегрузки.

Когда данные всё же прибывают быстрее, чем обрабатываются, применяют стратегии: буферизация (с лимитом), drop (отбрасывать новые), latest (хранить только последнее). Без управления backpressure быстрый источник вызвал бы OOM или деградацию.

Реактивный стек целиком

WebFlux по умолчанию работает на Netty (event loop), а не на Tomcat. Для неблокирующих зависимостей есть WebClient (неблокирующий HTTP-клиент, замена RestTemplate) и R2DBC (реактивный доступ к БД вместо блокирующего JDBC).

Критическое правило: вся цепочка должна быть неблокирующей. Один блокирующий вызов в request path — JDBC, Thread.sleep, .block() — занимает поток event loop, и поскольку этих потоков единицы, пропускная способность обрушивается. Неизбежно блокирующую работу выносят на отдельный планировщик (Schedulers.boundedElastic).

MVC, WebFlux и виртуальные потоки

MVC проще, привычнее и отлаживаемее; thread-per-request отлично подходит большинству CRUD-сервисов с JPA/JDBC. WebFlux оправдан при измеримой проблеме IO-concurrency, когда ВСЯ цепочка умеет non-blocking, а также для стриминга (Server-Sent Events, потоковые ответы) и явного backpressure.

Важный современный сдвиг: виртуальные потоки (Java 21) дают блокирующему MVC-коду масштаб реактивного без его сложности. Поэтому ради ОДНОГО ЛИШЬ масштаба на блокирующем IO WebFlux теперь берут реже — его уникальная ниша смещается к стримингу и backpressure, а не к «просто много соединений».

Цена сложности

Реактивный код труднее: stacktrace-ы малоинформативны (нужны onErrorContinue/checkpoint/reactor-tools), привычный ThreadLocal не работает (контекст пробрасывают через Reactor Context / Micrometer context propagation), и очень легко случайно заблокировать поток, не заметив этого.

Поэтому WebFlux — осознанный выбор под задачу, а не дефолт «чтобы было быстро». Команда должна уметь мыслить декларативными потоками и отлаживать их.

Как отвечать на собеседовании

Не сводитесь к «WebFlux быстрее». Объясните event loop (мало потоков на много соединений) против thread-per-request, Reactive Streams/Reactor (Mono/Flux, ленивость до subscribe), backpressure (request(n) против перегрузки) и требование сквозной неблокирующести (JDBC/.block убивают event loop → boundedElastic). Дайте формулу выбора: MVC по умолчанию; WebFlux — при доказанной IO-concurrency и end-to-end non-blocking или ради стриминга/backpressure; и что виртуальные потоки забрали у WebFlux аргумент «просто масштаб».

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем