Оглавление
- Введение
- Обзор Spring WebFlux
- Обзор Spring MVC с Virtual Threads (JDK 21+)
- Сравнение подходов
- Как выбрать стек: рекомендации
- Часто задаваемые вопросы (FAQ)
- Заключение
Введение: проблема конкурентности
Современные микросервисные системы обслуживают тысячи одновременных запросов, чаще всего – I/O-интенсивных (вызовы к БД, API, файловым системам и т.п.). Классическая модель «один поток на запрос» больше не масштабируется: тысячи ОС-потоков перегружают CPU и память. Поэтому появились два подхода:
- Spring WebFlux – реактивный неблокирующий стек с минимальным числом потоков (основан на Project Reactor).
- Spring MVC + Virtual Threads (VT) – классическая модель с лёгкими потоками, управляемыми JVM (проект Loom в JDK 21).
Обе технологии решают задачу конкурентности, но используют разные архитектурные принципы. В этой статье разберёмся, когда и что использовать.
Обзор: как работают подходы
Spring WebFlux (Reactor Netty)
- Обрабатывает запросы асинхронно через event-loop.
- Использует
Mono
,Flux
– реактивные типы. - Работает на Netty или асинхронных сервлетах (Servlet 3.1+).
- Минимальное потребление потоков, хорош для I/O-bound задач.
Плюсы:
- Высокая масштабируемость.
- Поддержка стриминга: WebSocket, SSE, RSocket.
- Эффективность при больших нагрузках.
Минусы:
- Требует знания реактивной модели.
- Сложнее отладка и трассировка.
- Проблемы при работе с blocking API (например, JDBC).
Spring MVC с Virtual Threads (JDK 21+)
- Каждый запрос получает свой виртуальный поток (легкий, управляется JVM).
- Работает с привычными API:
JdbcTemplate
,RestTemplate
,@Transactional
. - Запускается на Tomcat/Jetty с
spring.threads.virtual.enabled=true
.
Плюсы:
- Императивный стиль кода.
- Совместимость со всей экосистемой Spring.
- Безболезненная миграция старых приложений.
- Простота отладки, tracing и использования
ThreadLocal
.
Минусы:
- Возможны проблемы с pinned threads (например, native-код).
- Все еще зависит от CPU – если поток работает, он занимает ядро.
- Чуть выше накладные расходы при больших нагрузках, чем у WebFlux.
Сравнение подходов
Критерий | WebFlux (Reactor) | Spring MVC + VT |
---|---|---|
Модель выполнения | Асинхронная (event loop) | Поток-на-запрос (VT) |
Стиль кода | Функциональный, реактив | Императивный, привычный |
Совместимость с blocking API | Требуются обходы | Работает из коробки |
Использование ресурсов | Минимум потоков | Больше, но дёшево |
Кривая обучения | Высокая | Низкая |
Поддержка backpressure | Да | Нет |
Стриминг (WebSocket/SSE) | Отлично подходит | Требует обвязки |
Отладка и логгирование | Сложно (контекст теряется) | Привычные инструменты |
Поддержка ThreadLocal , MDC | Сложно | Да |
Совместим с Micrometer/OpenTelemetry | Сложнее | Да |
Как выбрать стек: рекомендации
Рекомендуется использовать WebFlux, если:
- Требуется экстремальная I/O масштабируемость.
- Используются WebSocket, SSE, RSocket.
- Все сервисы уже на реактивных драйверах (R2DBC, WebClient).
- Вы разрабатываете Gateway/API-Proxy.
- Критичны latency и количество потоков.
Рекомендуется использовать Spring MVC + Virtual Threads, если:
- Основной код синхронный (JDBC, JPA, RestTemplate).
- Требуется сохранить императивный стиль.
- Команда не знакома с реактивностью.
- Проект мигрирует с классического Spring MVC.
- Вы хотите масштабировать старое приложение без глобального рефакторинга.
Комбинированный подход
Многие проекты комбинируют оба подхода:
Компонент | Рекомендуемый стек |
---|---|
Gateway/Edge | WebFlux + Netty |
Бизнес-логика | Spring MVC + VT |
Реалтайм поток | WebFlux + SSE/WebSocket |
БД | VT + JDBC / Hibernate |
Часто задаваемые вопросы (FAQ)
1. WebFlux быстрее Virtual Threads?
Иногда.
WebFlux эффективен при I/O-нагрузке и стриминге. Virtual Threads ближе к нему по производительности, но могут проигрывать в CPU-биндинге или при большом числе pinned-потоков.
2. Virtual Threads могут полностью заменить WebFlux?
Нет.
WebFlux нужен там, где важны backpressure, push-механизмы (WebSocket, SSE), реактивные стримы. VT – хорошее решение для миграции и “простых” сервисов.
3. Можно ли комбинировать Web и WebFlux в одном приложении?
Да.
Spring Boot позволяет это. Но контроллеры должны быть изолированы – например, с помощью аннотаций @RestController
с разными base path (/api/*
и /r/*
) или запускаться по разным профилям.
4. Virtual Threads требуют перехода на R2DBC?
Нет.
Именно наоборот: их преимущество – полная совместимость с JDBC и другими блокирующими API.
5. Нужно ли переписывать код под Virtual Threads?
Нет.
Сохраняется обычный Spring MVC-код. Просто включается флаг spring.threads.virtual.enabled=true
.
6. Могу ли я использовать @Async
с Virtual Threads?
Да.
Начиная с Spring Boot 3.2, дефолтный SimpleAsyncTaskExecutor
использует Thread.ofVirtual().factory()
– т.е. @Async
автоматически работает на VT.
7. Работают ли MDC, ThreadLocal и Tracing с Virtual Threads?
Да.
Полная поддержка, как с обычными потоками. Это одно из больших преимуществ VT над WebFlux.
8. Есть ли ограничения у Virtual Threads в Spring?
Да.
- pinned threads (например, native-вызовы через JNI)
- ошибки в старых пулях потоков (Tomcat < 10.1.12)
- не все сторонние библиотеки оптимизированы под Loom
9. Можно ли использовать WebClient в MVC с виртуальными потоками?
Да.
WebClient работает нормально, но не даёт выигрыша в latency, так как вызывается из виртуального потока и сам – реактивный. Это допустимо, но не “обязательно”.
10. Что выбрать для бэка офиса или админки?
MVC + Virtual Threads.
Там нет стриминга, мало параллелизма – важна простота кода и совместимость. VT отлично справятся.
11. Работает ли Spring Security с Virtual Threads?
Да.
Spring Security 6 полностью совместим с VT. Контекст авторизации доступен как обычно.
12. Поддерживаются ли Virtual Threads в тестах (JUnit)?
Да.
JUnit 5.10+ позволяет запускать тесты на виртуальных потоках через настройку @Execution(ExecutionMode.CONCURRENT)
+ custom Executor
.
13. Можно ли использовать VT в GraalVM native-image?
Пока нет.
GraalVM не поддерживает Loom в native-image (по состоянию на 2025). Рекомендуется использовать обычные потоки.
14. Как логгировать traceId с Virtual Threads?
Через MDC.
Работает как с обычными потоками, если использовать Spring Sleuth или Micrometer Tracing. Главное – правильно инициализировать контекст.
15. Что будет, если вызвать blocking API внутри WebFlux?
Произойдёт блокировка event-loop и падение производительности.
Для этого WebFlux предлагает Schedulers.boundedElastic()
, но лучше избегать таких вызовов.
Заключение
Spring WebFlux и Spring MVC с Virtual Threads – два мощных подхода к масштабированию приложений. Первый – для реактивного, потокового и ресурсоэффективного I/O. Второй – для простоты, совместимости и быстрого старта на Java 21+. В ближайшие годы они будут сосуществовать, и выбор между ними должен опираться на:
- Характер нагрузки
- Уровень зрелости команды
- Требования к производительности и latency
- Готовность к смене архитектурного стека