Part 03 · Spring Framework & Spring Boot · 3.1.11

WebClient, WebFlux và reactive execution

Reactive stack tối ưu cho nhiều concurrent I/O khi toàn call path tôn trọng non-blocking execution. Nó không biến blocking dependency thành non-blocking chỉ bằng cách trả về Mono hoặc Flux.


Reactive Streams

Publisher phát onNext, onErroronComplete theo demand từ Subscriber. Mono biểu diễn 0..1 items, Flux 0..N. Pipeline lazy cho tới khi subscription xảy ra. Backpressure điều phối demand giữa reactive stages nhưng không tự giới hạn external API hoặc memory nếu operator buffering không bounded.

WebFlux architecture

WebFlux hỗ trợ annotated controllers và functional endpoints trên non-blocking runtime. Thay vì thread-per-request, một số ít event-loop threads xử lý nhiều connections; blocking call trên event loop làm trì hoãn hàng loạt requests chứ không chỉ request hiện tại.

Mô hìnhPhù hợpRủi ro
Servlet MVCBlocking ecosystem, JPA/JDBC, code imperative.Nhiều blocked threads khi concurrency cao.
WebFluxEnd-to-end non-blocking I/O, streaming, many concurrent connections.Event-loop stalls nếu blocking; reactive debugging/operations phức tạp hơn.
MVC + virtual threadsImperative blocking stack cần concurrency lớn trên Java hiện đại.Không loại connection-pool hoặc downstream capacity limits.

WebClient configuration

WebClient được cấu hình qua builder, filters, codecs và exchange strategies. Production client cần connection pool, connect/response/read/write timeout, max in-memory body, TLS/DNS policy và observability.

client.get().uri("/orders/{id}", id)
  .retrieve()
  .onStatus(HttpStatusCode::is5xxServerError, this::mapRemoteError)
  .bodyToMono(OrderDto.class)
  .timeout(Duration.ofSeconds(2));

retrieve() tiện cho status/body phổ biến; exchangeToMono() cho quyền kiểm soát response đầy đủ. Khi dùng API mức thấp, phải bảo đảm body/resource được consume hoặc release.

Blocking boundary

JDBC/JPA, legacy file I/O và blocking SDK vẫn block khi được bọc trong Mono. Có thể offload tạm thời sang boundedElastic, nhưng queue và threads vẫn hữu hạn. Nếu phần lớn stack blocking, MVC hoặc virtual threads có thể đơn giản và dễ vận hành hơn.

Không gọi block() trên event loop: nó có thể deadlock hoặc làm throughput collapse. Boundary imperative chỉ nên block ở nơi thread model cho phép và có timeout.

Context, errors và cancellation

Reactor Context giữ metadata subscriber-specific thay vì dựa vào ThreadLocal. Error là terminal signal; onErrorResume chỉ dùng cho fallback có semantics rõ. Retry cần idempotency, backoff, jitter và shared deadline. Cancellation nên propagate tới HTTP request hoặc upstream operation nếu client hỗ trợ.

Backpressure và buffering

Backpressure không bảo vệ một external service không hỗ trợ demand. Operators như buffer, window, flatMap và prefetch phải có concurrency/capacity phù hợp. Khi producer không thể chậm lại, cần drop, latest, bounded queue hoặc load shedding policy cùng metrics.

Testing reactive behavior

StepVerifier kiểm signals, error, completion và timing; virtual time phù hợp delay/retry. Integration test với server/client thật cần thiết cho codecs, pool, disconnect, cancellation và streaming. BlockHound có thể phát hiện blocking call nếu được cấu hình tương thích.

Operational checklist: đo event-loop latency, connection pool pending/acquired, response-body size, cancellation, retries và downstream latency. CPU thấp không chứng minh reactive service khỏe nếu event loop đang bị block.
Nguồn tham khảo