← Назад к вопросам

В чем разница между стартерами Web и WebFlux?

1.7 Middle🔥 131 комментариев
#Spring Boot и Spring Data#Spring Framework#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# 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, ResponseEntityMono, Flux, Publisher
Кривая обученияНизкаяВысокая
СовместимостьSpring MVCSpring WebFlux
БД драйверыСтандартные JDBCR2DBC (реактивные)
Внешние APIRestTemplateWebClient

Пример нагрузки

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);
    }
}

Лучшие практики

  1. Начинай с Web, если неясна нагрузка
  2. Переходи на WebFlux, если профилирование показывает проблемы с потоками
  3. Убедись, что БД драйвер реактивный (R2DBC)
  4. Используй WebClient вместо RestTemplate
  5. Обрабатывай ошибки в реактивных цепочках
  6. Тестируй с реальной нагрузкой перед выбором