Spring Cloud Gateway: một cửa vào cho cả hệ thống
Tiếp đi, giờ tới Gateway nhé.
Ở các bài trước, chúng ta đã có: Eureka để các service đăng ký và tìm nhau, Config Server để gom cấu hình. Bây giờ học Spring Cloud Gateway, thành phần đứng ở cửa ngõ của hệ thống microservices.
1. Bài toán: frontend phải gọi từng service
Project spring-cloud-demo đang có:
Order Service : localhost:8081
Payment Service : localhost:8082
Inventory Service : localhost:8083
Frontend phải gọi từng service:
fetch("http://localhost:8081/orders");
fetch("http://localhost:8082/payments");
fetch("http://localhost:8083/inventory");
Chạy được. Nhưng lên production thì bắt đầu rắc rối.
2. Vấn đề
- Frontend phải biết địa chỉ của từng service.
- Mỗi service tự xử lý CORS, authentication, logging, những việc giống hệt nhau.
- Đổi địa chỉ service là phải sửa frontend.
- Internal service bị expose thẳng ra Internet.
- Khó làm rate limiting tập trung.
3. Giải pháp: API Gateway
Đặt một API Gateway giữa client và microservices. Frontend chỉ gọi một domain:
fetch("https://api.example.com/orders");
fetch("https://api.example.com/payments");
Gateway chuyển request đến service tương ứng. Spring Cloud Gateway cung cấp:
| Chức năng | Giải thích |
|---|---|
| Routing | Chuyển request đến đúng service |
| Authentication | Kiểm tra thông tin xác thực tại gateway |
| Authorization | Áp dụng quy tắc truy cập phù hợp |
| Rate Limiting | Giới hạn tần suất request |
| Filters | Xử lý request/response trước và sau khi routing |
| Load Balancing | Phân phối request đến nhiều instance |
| CORS | Quản lý cross-origin requests |
| Observability | Metrics, tracing, logging |
Gateway có hai hướng: Server WebFlux (reactive, Reactor Netty) và Server Web MVC (Servlet). Bài này dùng WebFlux.
Ba khái niệm cốt lõi:
- Route: một luật chuyển tiếp, gồm
id,uriđích, predicates, filters. - Predicate: điều kiện khớp request (Path, Method, Header, Host...).
- Filter: sửa request/response trước và sau khi gọi service.
4. Code từng bước
spring-cloud-demo/
├── discovery-server/
├── config-server/
├── api-gateway/ <-- NEW
├── order-service/
├── payment-service/
└── inventory-service/
Bước 1: Dependency. Với các bản Spring Cloud mới, starter WebFlux là:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Dùng Spring Boot và Spring Cloud BOM tương thích. Không thêm spring-boot-starter-web vào Gateway WebFlux.
Bước 2: application.yml
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
server:
webflux:
routes:
- id: order-route
uri: lb://order-service
predicates:
- Path=/orders/**
- id: payment-route
uri: lb://payment-service
predicates:
- Path=/payments/**
- id: inventory-route
uri: lb://inventory-service
predicates:
- Path=/inventory/**
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Bản cũ hơn dùng spring.cloud.gateway.routes thay vì spring.cloud.gateway.server.webflux.routes.
id: tên route.uri: lb://order-service:lb://nghĩa là dùng Spring Cloud LoadBalancer;order-servicelà service ID trên Eureka. Gateway tìm và chọn một instance.Path=/orders/**: điều kiện áp dụng route. Không cấu hình rewrite/strip prefix nên path giữ nguyên khi forward.
Bước 3: Code Java. Order Service có API:
@RestController
@RequestMapping("/orders")
public class OrderController {
@GetMapping("/{id}")
public String getOrder(@PathVariable Long id) {
return "Order ID: " + id;
}
}
Thêm một GlobalFilter ở Gateway để log thời gian xử lý:
@Component
public class RequestLoggingFilter implements GlobalFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long start = System.currentTimeMillis();
String path = exchange.getRequest().getURI().getPath();
log.info("Incoming request: {}", path);
return chain.filter(exchange)
.doFinally(signal -> log.info("Request finished: {}, duration={}ms",
path, System.currentTimeMillis() - start));
}
@Override
public int getOrder() {
return -1;
}
}
chain.filter(exchange) trả về Mono<Void>, đại diện cho quá trình xử lý bất đồng bộ. doFinally chạy khi sequence kết thúc, lỗi hoặc bị cancel. Không có Thread.sleep(), không chờ đồng bộ.
Bước 4: Test bằng curl
# Gọi trực tiếp (client phải biết port)
curl http://localhost:8081/orders/123
# Gọi qua Gateway
curl http://localhost:8080/orders/123
# Order ID: 123
Client không cần biết Order Service chạy port nào.
5. Cách hoạt động
GET /orders/123
|
v
Gateway :8080
1. RoutePredicateHandlerMapping: khớp Path=/orders/**
2. Chạy filter chain (pre)
3. lb://order-service → hỏi LoadBalancer → Eureka
|
v
Order Service :8081 (một trong N instance)
|
v
GET /orders/123 → response
|
4. Filter chain (post)
v
Client
Filter chain dưới dạng khái niệm:
Client Request
|
[Authentication Filter]
|
[Logging Filter]
|
[Routing Filter] ---> Order Service
|
[Response Processing]
|
Client Response
Trong Gateway WebFlux, GlobalFilter và GatewayFilter được sắp thành một chuỗi theo thứ tự; Spring Security dùng WebFilter chain riêng.
Gateway và Event Loop. Gateway WebFlux chạy trên Reactor Netty, mô hình event loop. 100 request chờ downstream 2 giây không cần 100 thread blocking. Nhưng nếu filter chặn event loop thì cả Gateway chậm theo:
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// BAD: Blocking event loop!
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return chain.filter(exchange);
}
Gọi JDBC hoặc OpenFeign đồng bộ trong filter cũng vậy. Nếu buộc dùng blocking API, offload sang Schedulers.boundedElastic() và giới hạn concurrency. Đây chỉ là biện pháp tương thích, không biến nó thành non-blocking thật.
Rate limiting. Gateway có filter RequestRateLimiter, thường dùng Redis (thuật toán Token Bucket, chạy bằng Lua script atomic):
User A ── 10 req/s ──> Gateway → Allowed
User A ── vượt quota ─> Gateway → 429 Too Many Requests
Authentication ở Gateway có đủ không? Gateway có thể validate JWT trước khi route:
Client ── JWT ──> API Gateway ── validate
|
+-- Invalid --> 401 Unauthorized
+-- Valid ----> Order Service
Nhưng không nên coi đó là đủ. Service vẫn cần authorization theo nghiệp vụ: User A không được xem đơn của User B, chỉ Admin được làm một số thao tác, internal call phải được tin cậy đúng cách.
6. So sánh: Spring Cloud Gateway và Nginx
| Tiêu chí | Nginx | Spring Cloud Gateway |
|---|---|---|
| Bản chất | Web server / Reverse proxy | Application-level API Gateway |
| Runtime | C, event-driven | Java, Spring |
| Routing, Load Balancing | Có | Có |
| TLS termination | Có | Có thể cấu hình |
| Custom business-aware filter | Mở rộng được, tùy module | Viết bằng Java, dễ tích hợp Spring |
| Eureka | Không tích hợp sẵn | Tích hợp qua Spring Cloud (lb://) |
| Spring Security | Không | Có thể tích hợp |
Hai thứ không nhất thiết thay thế nhau:
Internet
|
Nginx / Cloud Load Balancer (TLS termination, reverse proxy)
|
Spring Cloud Gateway (authentication, API routing, rate limiting)
|
Microservices
7. Khi nào nên dùng / không nên dùng
- Nên: hệ thống Spring nhiều service, cần một entry point, cần filter viết bằng Java (JWT, header, logging), dùng Eureka.
- Không cần: chỉ có 1–2 service; Ingress / Azure Application Gateway / API Management đã đáp ứng đủ routing và policy; team không muốn tự vận hành thêm một service.
Trước khi qua phần mới, mình hỏi về Spring Cloud Gateway. Cloud thường đã có gateway sẵn (hoặc dùng load balancer làm gateway như trên Azure). Nếu dùng Spring Cloud Gateway thì chỉ cần một VM rồi deploy Spring Cloud Gateway lên là được, đúng không?
Đúng rồi! Spring Cloud Gateway thực chất là một ứng dụng Spring Boot, nên bạn hoàn toàn có thể tạo 1 VM trên Azure hoặc AWS, deploy Gateway lên đó (JAR hoặc Docker) và dùng nó làm API Gateway. Nhưng có một điểm quan trọng: Cloud Load Balancer và Spring Cloud Gateway không hoàn toàn giống nhau.
1. Kiến trúc khi deploy Gateway trên VM
Internet
| HTTPS
v
Public IP / DNS
|
v
Azure VM (Linux) :8080
+---------------------+
| Spring Cloud Gateway|
+---------------------+
|
+----------+----------+
v v v
Order Payment Inventory
VM 2 VM 3 VM 4
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: order-service
uri: http://10.0.0.5:8081
predicates:
- Path=/orders/**
- id: payment-service
uri: http://10.0.0.6:8082
predicates:
- Path=/payments/**
GET https://api.example.com/orders/123
→ Spring Cloud Gateway
→ http://10.0.0.5:8081/orders/123
2. Azure Load Balancer khác Spring Cloud Gateway thế nào?
| Thành phần | Vai trò |
|---|---|
| Azure Load Balancer | Layer 4, phân phối TCP/UDP traffic |
| Azure Application Gateway | Layer 7, HTTP routing, TLS termination, WAF tùy cấu hình |
| Azure API Management | API policies, authentication integration, quotas, subscriptions, analytics |
| Spring Cloud Gateway | Layer 7, routing, filters, Spring Security integration, rate limiting |
Khác biệt lớn: Application Gateway là dịch vụ Azure quản lý; Spring Cloud Gateway là ứng dụng bạn tự deploy và vận hành. Có chức năng trùng nhau nhưng không nhất thiết thay thế nhau.
3. Kết hợp được không?
Có, và đây là kiến trúc hợp lý nếu cần cả bảo vệ ở edge lẫn xử lý API đặc thù:
Internet
|
Azure Application Gateway
- Public HTTPS, TLS termination
- WAF (nếu bật), health probes
|
Spring Cloud Gateway
- JWT validation, API routing
- Custom filters, rate limiting
|
+-------+-------+
v v
Order Service Payment Service
Nếu Application Gateway đã đáp ứng hết routing, auth và policy thì không cần thêm Spring Cloud Gateway.
4. Chỉ 1 VM có ổn không? SPOF
Được cho học tập, DEV, hệ thống nhỏ. Production phải tính Single Point of Failure:
Internet
|
Gateway VM 1 ❌ DOWN
X
All backend services become unreachable
Order và Payment vẫn sống, nhưng client không vào được. Kiến trúc HA:
Internet
|
Azure Load Balancer
(hoặc Application Gateway)
|
+------+------+
v v
Gateway VM 1 Gateway VM 2
+------+------+
|
Backend Services
- Hai Gateway instance chạy độc lập.
- Load Balancer có health probe, không gửi request vào instance lỗi.
- Một VM down, VM còn lại tiếp tục phục vụ nếu đủ capacity.
- Gateway nên stateless (rate limit state để ở Redis) để dễ scale.
5. Gateway không bắt buộc dùng Eureka
# IP cố định
uri: http://10.0.0.5:8081
# Có Eureka
uri: lb://order-service
# Kubernetes Service DNS
uri: http://order-service:8080
Tóm lại: Azure Application Gateway quản lý traffic ở tầng cloud infrastructure, còn Spring Cloud Gateway xử lý routing và API policy ở tầng ứng dụng. Dùng riêng hoặc kết hợp tùy yêu cầu.
Trong custom filter của Gateway mình gọi OpenFeign để check user, Payment mất 5 giây phản hồi. Gateway WebFlux nên chắc không sao nhỉ?
Chưa đúng. Gateway dùng WebFlux không có nghĩa mọi thứ bên trong tự non-blocking. OpenFeign là blocking: thread gọi sẽ đứng chờ 5 giây. Nếu đó là event loop thread, nó không xử lý được request khác trong lúc chờ. Event loop chỉ có số thread bằng khoảng số CPU core, nên vài request chậm là đủ làm cả Gateway nghẽn.
Event loop thread #1
├─ request A: filter gọi Feign ... chờ 5s (BLOCKED)
├─ request B: xếp hàng
└─ request C: xếp hàng
Cách đúng: dùng WebClient (non-blocking) trong filter, hoặc validate JWT cục bộ bằng public key thay vì gọi service khác. Gateway cũng không tự biến OpenFeign thành non-blocking.
Phỏng vấn hỏi về API Gateway / Spring Cloud Gateway thì trả lời sao?
Q1. Spring Cloud Gateway là gì, sao cần API Gateway?
🇻🇳 Tiếng Việt:
Spring Cloud Gateway là API Gateway trong hệ sinh thái Spring. Nó là điểm vào duy nhất cho client, gom các việc chung như routing, authentication, logging, rate limiting về một chỗ. Mỗi route gồm URI đích, predicate để khớp request và filter để sửa request/response. Với
lb://order-service, nó dùng Eureka và Spring Cloud LoadBalancer để chọn instance.
🇬🇧 English:
Spring Cloud Gateway is an API Gateway in the Spring ecosystem. It provides a single entry point for clients and centralizes cross-cutting concerns such as routing, authentication, logging and rate limiting. Each route has a target URI, predicates to match requests and filters to modify requests and responses. With
lb://order-serviceit uses service discovery and Spring Cloud LoadBalancer to pick an instance.
Q2. Spring Cloud Gateway khác Nginx / Azure Application Gateway thế nào?
🇻🇳 Tiếng Việt:
Nginx là web server, reverse proxy đa dụng. Azure Application Gateway là dịch vụ Layer 7 do cloud quản lý, mạnh về TLS và WAF. Spring Cloud Gateway là ứng dụng mình tự vận hành, mạnh ở logic tầng ứng dụng: filter bằng Java, Spring Security, tích hợp Eureka. Thường đặt cloud gateway ở edge, Spring Cloud Gateway phía sau.
🇬🇧 English:
Nginx is a general-purpose web server and reverse proxy. Azure Application Gateway is a managed Layer 7 service, strong at TLS termination and WAF. Spring Cloud Gateway is an application we run ourselves, and it is strong at application-level logic: Java filters, Spring Security and Eureka integration. A common setup puts the cloud gateway at the edge and Spring Cloud Gateway behind it.
Q3. Deploy Spring Cloud Gateway trên một VM được không?
🇻🇳 Tiếng Việt:
Được, vì nó là một ứng dụng Spring Boot, chạy bằng JAR hoặc Docker. Nhưng production thì một VM là single point of failure. Em sẽ chạy ít nhất hai instance stateless sau Azure Load Balancer hoặc Application Gateway có health probe.
🇬🇧 English:
Yes. Spring Cloud Gateway is a Spring Boot application, so we can deploy it on an Azure VM as a JAR or a Docker container. For production, a single VM is a single point of failure, so I would run at least two stateless instances behind Azure Load Balancer or Application Gateway with health probes.
Q4. Gateway có non-blocking không? Có thay Spring Security ở service không?
🇻🇳 Tiếng Việt:
Bản WebFlux dùng Reactor Netty, non-blocking, chịu được nhiều request đồng thời với ít thread, miễn là không gọi blocking (JDBC, Feign, sleep) trên event loop. Gateway xác thực tập trung được, nhưng service vẫn phải tự authorization theo nghiệp vụ.
🇬🇧 English:
The WebFlux-based gateway uses Reactor Netty and non-blocking I/O, so it handles many concurrent requests with few threads, as long as we never block event-loop threads with JDBC, Feign or sleep. The gateway can do centralized authentication, but each service still enforces its own business authorization.
Cách nhớ ngắn gọn
- Gateway = một cửa vào: Route = URI + Predicate + Filter.
lb://service-id= Eureka + LoadBalancer.- WebFlux Gateway: không bao giờ block event loop.
- Cloud gateway ở edge, Spring Cloud Gateway ở tầng ứng dụng; chạy ≥ 2 instance để tránh SPOF.
Tự kiểm tra
uri: lb://order-service nghĩa là gì?
Dùng Spring Cloud LoadBalancer, lấy danh sách instance của service ID order-service từ Eureka rồi chọn một instance.
Vì sao không thêm spring-boot-starter-web vào Gateway WebFlux?
Gateway WebFlux chạy trên Reactor Netty. Thêm starter web (Servlet/Tomcat) sẽ xung đột mô hình chạy.
Gọi OpenFeign đồng bộ trong GlobalFilter có vấn đề gì?
Block event loop thread, giảm mạnh khả năng xử lý request của Gateway. Dùng WebClient hoặc validate JWT cục bộ.
Azure Load Balancer và Application Gateway khác nhau chỗ nào?
Load Balancer là Layer 4 (TCP/UDP). Application Gateway là Layer 7 (HTTP routing, TLS termination, WAF).
Chỉ có 1 Gateway VM thì rủi ro gì, khắc phục ra sao?
SPOF: VM chết thì client không vào được backend. Chạy ≥ 2 instance stateless sau load balancer có health probe.