В чем разница между стартерами Web и WebFlux?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Spring Boot Web vs WebFlux
Это два принципиально разных подхода к созданию веб-приложений в Spring Boot. Выбор между ними зависит от ваших требований к производительности, типу приложения и особенностям обработки запросов.
Spring Boot Web (spring-boot-starter-web)
Характеристики
spring-boot-starter-web использует традиционный синхронный, блокирующий подход на основе сервлетов.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Как это работает
Клиент отправляет запрос
↓
Одно из потоков из пула (Thread Pool) обрабатывает запрос
↓
Поток блокируется во время обработки (БД, API, I/O операции)
↓
Поток ждёт ответа
↓
Поток отправляет ответ клиенту
↓
Поток возвращается в пул потоков
Пример контроллера
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// Синхронный, блокирующий способ
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUserById(@PathVariable Long id) {
// Это потокобезопасно, но поток блокируется здесь
UserDto user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<UserDto> createUser(@RequestBody CreateUserRequest request) {
// Поток будет заблокирован во время сохранения в БД
UserDto createdUser = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}
}
Технологии
- Сервлет-контейнер: Tomcat (по умолчанию), Jetty, Undertow
- Framework: Spring MVC
- Модель: Один поток на один запрос
- Рабочий поток: Синхронный
Spring Boot WebFlux (spring-boot-starter-webflux)
Характеристики
spring-boot-starter-webflux использует асинхронный, неблокирующий подход на основе реактивного программирования.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Как это работает
Клиент отправляет запрос
↓
Маленький пул потоков (Event Loop) начинает обработку
↓
Поток НЕ блокируется, передаёт управление (Non-blocking I/O)
↓
Когда операция готова (БД ответила, API ответил), callback уведомляет
↓
Поток возвращается и обрабатывает результат
↓
Отправляет ответ клиенту
Пример контроллера
@RestController
@RequestMapping("/api/users")
public class UserReactiveController {
@Autowired
private UserReactiveService userService;
// Асинхронный, неблокирующий способ
@GetMapping("/{id}")
public Mono<UserDto> getUserById(@PathVariable Long id) {
// Возвращает Mono (0 или 1 элемент)
return userService.getUserById(id);
}
@GetMapping
public Flux<UserDto> getAllUsers() {
// Возвращает Flux (0 или более элементов)
return userService.getAllUsers();
}
@PostMapping
public Mono<UserDto> createUser(@RequestBody CreateUserRequest request) {
// Асинхронное сохранение, не блокирует поток
return userService.createUser(request);
}
}
Реактивный сервис
@Service
public class UserReactiveService {
@Autowired
private UserRepository userRepository; // Должен быть реактивным
public Mono<UserDto> getUserById(Long id) {
return userRepository.findById(id)
.map(this::convertToDto)
.switchIfEmpty(Mono.error(
new UserNotFoundException("User not found")
));
}
public Flux<UserDto> getAllUsers() {
return userRepository.findAll()
.map(this::convertToDto);
}
public Mono<UserDto> createUser(CreateUserRequest request) {
User user = new User();
user.setEmail(request.getEmail());
user.setName(request.getName());
return userRepository.save(user)
.map(this::convertToDto);
}
private UserDto convertToDto(User user) {
return new UserDto(user.getId(), user.getEmail(), user.getName());
}
}
Технологии
- Сервер: Netty (по умолчанию), Tomcat, Jetty, Undertow
- Framework: Spring WebFlux
- Модель: Event Loop + Reactive Streams
- Рабочий поток: Асинхронный
Детальное сравнение
| Аспект | Web (Servlet) | WebFlux (Reactive) |
|---|---|---|
| Model | Синхронный, блокирующий | Асинхронный, неблокирующий |
| Потоки | Один на запрос | Небольшой пул (Event Loop) |
| Масштабируемость | До ~200 одновременных запросов | 1000+ одновременных запросов |
| Задержка | Выше при большой нагрузке | Ниже при большой нагрузке |
| I/O операции | Блокирующие | Неблокирующие |
| Возвращаемый тип | Object, ResponseEntity | Mono, Flux, Publisher |
| Кривая обучения | Низкая | Высокая |
| Совместимость | Spring MVC | Spring WebFlux |
| БД драйверы | Стандартные JDBC | R2DBC (реактивные) |
| Внешние API | RestTemplate | WebClient |
Пример нагрузки
Web (Servlet) — 10 одновременных запросов
ОС выделяет потоки:
Поток 1 — Запрос 1 (блокирован на БД, ждёт 100ms)
Поток 2 — Запрос 2 (блокирован на БД, ждёт 100ms)
Поток 3 — Запрос 3 (блокирован на БД, ждёт 100ms)
...
Поток 10 — Запрос 10 (блокирован на БД, ждёт 100ms)
Всего 10 потоков заняты, каждый ждёт 100ms
Операционная система переключает контекст между потоками
Потребление памяти: ~10 потоков x ~1MB = 10MB+
WebFlux (Reactive) — 1000 одновременных запросов
ОС использует Event Loop:
Поток 1 — Обработка запроса 1 (1ms)
Поток 1 — Обработка запроса 2 (1ms)
Поток 1 — Обработка запроса 3 (1ms)
...
Поток 1 — Обработка запроса 1000 (1ms)
Когда БД отвечает на запрос 1, callback обрабатывает результат
Малое количество потоков (~4-8), но высокий throughput
Потребление памяти: ~4 потока x 1MB = ~4MB
Когда использовать Web
✓ Традиционные веб-приложения с умеренной нагрузкой
✓ Администраторам проще обслуживать
✓ Команде требуется меньше опыта Reactive программирования
✓ Монолиты с обычной архитектурой
✓ Имеется хороший JDBC драйвер для вашей БД
✓ I/O операции редкие и недолгие
Когда использовать WebFlux
✓ Микросервисы с высокой нагрузкой
✓ API Gateway — нужна масштабируемость
✓ Streaming — нужно отправлять большие объёмы данных
✓ WebSockets — реал-тайм коммуникация
✓ IoT приложения — много одновременных подключений
✓ Финансовые системы с высокой throughput
✓ Real-time данные — требуется быстрая обработка
Сравнение производительности
Запросы в секунду на стандартном сервере (4 ядра, 4GB RAM)
Тест: 10,000 одновременных соединений
Web (Servlet): ~5,000 req/s
WebFlux: ~50,000 req/s
Учитываются:
- Задержка БД: 50ms
- Задержка API: 100ms
- Обработка: 10ms
Миграция с Web на WebFlux
// ДО: Web (Servlet)
@GetMapping("/users/{id}")
public UserDto getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
return convertToDto(user);
}
// ПОСЛЕ: WebFlux (Reactive)
@GetMapping("/users/{id}")
public Mono<UserDto> getUser(@PathVariable Long id) {
return userService.getUserById(id)
.map(this::convertToDto);
}
Гибридный подход
Можно использовать WebFlux только для критичных по производительности endpoint'ов:
public class MixedController {
// WebFlux для высоконагруженного endpoint
@GetMapping("/api/stream/events")
public Flux<Event> streamEvents() {
return eventService.getEventStream();
}
// Web остаётся для обычных операций
@PostMapping("/api/admin/config")
public ResponseEntity<ConfigDto> updateConfig(@RequestBody ConfigRequest request) {
ConfigDto config = configService.update(request);
return ResponseEntity.ok(config);
}
}
Лучшие практики
- Начинай с Web, если неясна нагрузка
- Переходи на WebFlux, если профилирование показывает проблемы с потоками
- Убедись, что БД драйвер реактивный (R2DBC)
- Используй WebClient вместо RestTemplate
- Обрабатывай ошибки в реактивных цепочках
- Тестируй с реальной нагрузкой перед выбором