Phỏng vấn Spring Cloud Gateway: Routing, Filter và Load Balancing
Làm chủ Spring Cloud Gateway cho phỏng vấn kỹ thuật: 12 câu hỏi về routing, filter, load balancing và mẫu API Gateway kèm ví dụ mã.

Spring Cloud Gateway là giải pháp tham chiếu để triển khai API Gateway trong kiến trúc microservices Spring. Các buổi phỏng vấn kỹ thuật đánh giá năng lực cấu hình routing, tạo filter tùy chỉnh và quản lý load balancing một cách hiệu quả.
Nhà tuyển dụng kiểm tra mức hiểu các mẫu Gateway: xác thực tập trung, rate limiting và circuit breaker. Việc lý giải vì sao chọn Spring Cloud Gateway thay vì các giải pháp thay thế sẽ tạo ra khác biệt.
Kiến trúc và nguyên lý cơ bản của Spring Cloud Gateway
Câu hỏi 1: Spring Cloud Gateway là gì và tại sao nên dùng?
Spring Cloud Gateway là một API Gateway phản ứng (reactive) được xây dựng trên Spring WebFlux và Project Reactor. Nó đóng vai trò điểm vào duy nhất cho mọi yêu cầu tới các microservices, cung cấp khả năng routing, lọc và load balancing.
// Cấu hình cơ bản của Spring Cloud Gateway
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
// Khởi động máy chủ phản ứng Netty (không phải Tomcat)
SpringApplication.run(GatewayApplication.class, args);
}
}# application.yml
# Cấu hình tối thiểu cho gateway
spring:
cloud:
gateway:
routes:
# Route đến dịch vụ người dùng
- id: user-service
uri: http://localhost:8081
predicates:
- Path=/api/users/**
# Route đến dịch vụ đơn hàng
- id: order-service
uri: http://localhost:8082
predicates:
- Path=/api/orders/**Ưu điểm chính của Spring Cloud Gateway: kiến trúc không chặn cho hiệu năng cao, tích hợp gốc với hệ sinh thái Spring Cloud và hỗ trợ các mẫu phản ứng hiện đại.
Câu hỏi 2: Hãy giải thích các khái niệm Route, Predicate và Filter
Ba khái niệm cốt lõi định hình Spring Cloud Gateway: Route xác định đích đến, Predicate quyết định khi nào áp dụng route, và Filter chỉnh sửa yêu cầu cũng như phản hồi.
// Cấu hình route bằng mã nguồn
@Configuration
public class RouteConfiguration {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// Route với nhiều predicate
.route("product-service", r -> r
// Predicate: đường dẫn URL
.path("/api/products/**")
// Predicate: phương thức HTTP
.and()
.method(HttpMethod.GET, HttpMethod.POST)
// Predicate: header có mặt
.and()
.header("X-Api-Version", "v2")
// Filter: viết lại đường dẫn
.filters(f -> f
.rewritePath("/api/products/(?<segment>.*)", "/products/${segment}")
// Filter: thêm header
.addRequestHeader("X-Gateway-Source", "spring-cloud-gateway")
)
// URI đích
.uri("http://localhost:8083"))
.build();
}
}Dòng xử lý diễn ra theo trình tự sau:
Yêu cầu đến
│
▼
┌─────────────────┐
│ Predicates │ → Đánh giá điều kiện (path, method, header...)
└────────┬────────┘
│ Tìm thấy khớp
▼
┌─────────────────┐
│ Pre-Filters │ → Sửa yêu cầu trước routing
└────────┬────────┘
│
▼
┌─────────────────┐
│ HTTP Proxy │ → Chuyển tiếp tới dịch vụ đích
└────────┬────────┘
│
▼
┌─────────────────┐
│ Post-Filters │ → Sửa phản hồi trước khi trả về cho client
└────────┬────────┘
│
▼
Phản hồi tới clientCâu hỏi 3: Các predicate thông dụng nhất là gì?
Spring Cloud Gateway cung cấp nhiều predicate có sẵn cho các điều kiện routing đa dạng. Kết hợp nhiều predicate cho phép tạo ra các quy tắc routing tinh vi.
# application.yml
# Ví dụ predicate phổ biến
spring:
cloud:
gateway:
routes:
# Routing theo path với bắt biến
- id: user-details
uri: http://user-service
predicates:
- Path=/users/{userId}
# Routing theo phương thức HTTP
- id: user-create
uri: http://user-service
predicates:
- Path=/users
- Method=POST
# Routing theo header
- id: mobile-api
uri: http://mobile-service
predicates:
- Header=X-Client-Type, mobile
# Routing theo tham số query
- id: search-api
uri: http://search-service
predicates:
- Query=q
# Routing theo host
- id: admin-portal
uri: http://admin-service
predicates:
- Host=admin.example.com
# Routing theo thời gian
- id: maintenance-mode
uri: http://maintenance-service
predicates:
- Between=2026-03-20T02:00:00Z,2026-03-20T04:00:00Z// Tạo predicate tùy chỉnh
@Component
public class ApiKeyRoutePredicateFactory
extends AbstractRoutePredicateFactory<ApiKeyRoutePredicateFactory.Config> {
public ApiKeyRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// Kiểm tra sự hiện diện và tính hợp lệ của API key
String apiKey = exchange.getRequest()
.getHeaders()
.getFirst("X-Api-Key");
return apiKey != null && config.getValidKeys().contains(apiKey);
};
}
@Validated
public static class Config {
private List<String> validKeys = new ArrayList<>();
public List<String> getValidKeys() {
return validKeys;
}
public void setValidKeys(List<String> validKeys) {
this.validKeys = validKeys;
}
}
}Thứ tự predicate không ảnh hưởng đến đánh giá, nhưng thứ tự route lại quan trọng. Các route được đánh giá tuần tự và route khớp đầu tiên sẽ được sử dụng.
Filter và biến đổi yêu cầu
Câu hỏi 4: Filter pre và post-processing hoạt động như thế nào?
Các filter GatewayFilter chạy trong một chuỗi có thứ tự. Filter "pre" sửa yêu cầu trước routing, filter "post" sửa phản hồi sau khi nhận được từ dịch vụ đích.
// Filter logging toàn cục
@Component
@Slf4j
public class LoggingFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// PRE-FILTER: trước routing
String requestId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
log.info("Request {} started: {} {}",
requestId,
exchange.getRequest().getMethod(),
exchange.getRequest().getPath());
// Thêm ID yêu cầu vào header
ServerHttpRequest modifiedRequest = exchange.getRequest()
.mutate()
.header("X-Request-Id", requestId)
.build();
// Tiếp tục chuỗi và xử lý phản hồi
return chain.filter(exchange.mutate().request(modifiedRequest).build())
.then(Mono.fromRunnable(() -> {
// POST-FILTER: sau khi có phản hồi
long duration = System.currentTimeMillis() - startTime;
HttpStatusCode status = exchange.getResponse().getStatusCode();
log.info("Request {} completed: status={}, duration={}ms",
requestId, status, duration);
}));
}
@Override
public int getOrder() {
// Thứ tự âm = chạy đầu tiên
return -1;
}
}// Filter xác thực JWT
@Component
@RequiredArgsConstructor
public class AuthenticationFilter implements GatewayFilter {
private final JwtTokenValidator tokenValidator;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String authHeader = exchange.getRequest()
.getHeaders()
.getFirst(HttpHeaders.AUTHORIZATION);
// Kiểm tra sự hiện diện của token
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return handleUnauthorized(exchange, "Missing or invalid Authorization header");
}
String token = authHeader.substring(7);
// Xác thực token theo cách phản ứng
return tokenValidator.validate(token)
.flatMap(claims -> {
// Bổ sung yêu cầu với thông tin người dùng
ServerHttpRequest enrichedRequest = exchange.getRequest()
.mutate()
.header("X-User-Id", claims.getSubject())
.header("X-User-Roles", String.join(",", claims.getRoles()))
.build();
return chain.filter(exchange.mutate().request(enrichedRequest).build());
})
.onErrorResume(e -> handleUnauthorized(exchange, e.getMessage()));
}
private Mono<Void> handleUnauthorized(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = String.format("{\"error\": \"%s\"}", message);
DataBuffer buffer = exchange.getResponse()
.bufferFactory()
.wrap(body.getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Mono.just(buffer));
}
}Câu hỏi 5: Các filter có sẵn nào hữu ích nhất?
Spring Cloud Gateway cung cấp nhiều filter có sẵn cho các tình huống thường gặp: viết lại URL, sửa header, retry và circuit breaker.
# application.yml
# Các filter có sẵn phổ biến
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
# Viết lại path
- RewritePath=/api/orders/(?<segment>.*), /orders/${segment}
# Thêm header yêu cầu
- AddRequestHeader=X-Gateway-Version, 1.0
# Loại bỏ header phản hồi nhạy cảm
- RemoveResponseHeader=X-Powered-By
- RemoveResponseHeader=Server
# Tiền tố path
- PrefixPath=/v2
# Loại bỏ tiền tố
- StripPrefix=1
# Tự động retry khi lỗi
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
methods: GET
backoff:
firstBackoff: 100ms
maxBackoff: 500ms
factor: 2
# Giới hạn tốc độ
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"// Cấu hình rate limiter theo người dùng
@Configuration
public class RateLimiterConfiguration {
@Bean
public KeyResolver userKeyResolver() {
// Giới hạn theo người dùng đã xác thực
return exchange -> Mono.just(
exchange.getRequest()
.getHeaders()
.getFirst("X-User-Id")
).defaultIfEmpty("anonymous");
}
@Bean
public KeyResolver ipKeyResolver() {
// Giới hạn theo địa chỉ IP
return exchange -> Mono.just(
Objects.requireNonNull(exchange.getRequest()
.getRemoteAddress())
.getAddress()
.getHostAddress()
);
}
}Sẵn sàng chinh phục phỏng vấn Spring Boot?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Câu hỏi 6: Làm thế nào để triển khai filter sửa body?
Sửa body của yêu cầu hoặc phản hồi đòi hỏi cách tiếp cận đặc thù với ModifyRequestBodyGatewayFilterFactory hoặc ModifyResponseBodyGatewayFilterFactory.
// Filter sửa body của yêu cầu
@Component
@RequiredArgsConstructor
public class RequestBodyModificationFilter implements GlobalFilter, Ordered {
private final ObjectMapper objectMapper;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Chỉ sửa các yêu cầu POST/PUT có JSON
if (!isJsonRequest(exchange)) {
return chain.filter(exchange);
}
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
try {
// Parse và sửa JSON
Map<String, Object> body = objectMapper.readValue(
bytes,
new TypeReference<Map<String, Object>>() {}
);
// Thêm metadata
body.put("processedAt", Instant.now().toString());
body.put("gatewayVersion", "1.0");
byte[] modifiedBytes = objectMapper.writeValueAsBytes(body);
// Tạo yêu cầu mới với body đã sửa
ServerHttpRequest modifiedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()
) {
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(
exchange.getResponse()
.bufferFactory()
.wrap(modifiedBytes)
);
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.putAll(super.getHeaders());
headers.setContentLength(modifiedBytes.length);
return headers;
}
};
return chain.filter(exchange.mutate().request(modifiedRequest).build());
} catch (IOException e) {
return Mono.error(new RuntimeException("Failed to parse request body", e));
}
});
}
private boolean isJsonRequest(ServerWebExchange exchange) {
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
return contentType != null &&
contentType.isCompatibleWith(MediaType.APPLICATION_JSON);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}// Cấu hình để sửa các phản hồi
@Configuration
public class ResponseBodyModificationConfig {
@Bean
public RouteLocator responseModifyingRoutes(
RouteLocatorBuilder builder,
ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilter
) {
return builder.routes()
.route("modify-response", r -> r
.path("/api/users/**")
.filters(f -> f.modifyResponseBody(
String.class,
String.class,
MediaType.APPLICATION_JSON_VALUE,
(exchange, responseBody) -> {
// Bao phản hồi trong định dạng chuẩn
return Mono.just(String.format(
"{\"success\": true, \"data\": %s, \"timestamp\": \"%s\"}",
responseBody,
Instant.now()
));
}
))
.uri("lb://user-service"))
.build();
}
}Load Balancing và khả năng phục hồi
Câu hỏi 7: Cấu hình load balancing với Spring Cloud LoadBalancer như thế nào?
Spring Cloud Gateway tích hợp với Spring Cloud LoadBalancer để phân phối lưu lượng giữa các instance của dịch vụ. Sơ đồ URI lb:// kích hoạt load balancing tự động.
# application.yml
# Cấu hình load balancing
spring:
cloud:
gateway:
routes:
- id: user-service
# lb:// kích hoạt load balancing
uri: lb://user-service
predicates:
- Path=/api/users/**
# Cấu hình load balancer
loadbalancer:
ribbon:
enabled: false # Dùng Spring Cloud LoadBalancer (không phải Ribbon)
# Cấu hình theo dịch vụ
configurations: default
# Health check cho load balancing
health-check:
path:
user-service: /actuator/health
interval: 10s
# Service discovery (Eureka hoặc khác)
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/// Cấu hình load balancer tùy chỉnh
@Configuration
@LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfig.class)
public class LoadBalancerConfiguration {
}
// CustomLoadBalancerConfig.java
// Chiến lược load balancing tùy chỉnh
public class CustomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> loadBalancer(
Environment environment,
LoadBalancerClientFactory clientFactory
) {
String serviceId = environment.getProperty(
LoadBalancerClientFactory.PROPERTY_NAME
);
// Round Robin theo mặc định
return new RoundRobinLoadBalancer(
clientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
serviceId
);
}
@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier(
ConfigurableApplicationContext context
) {
// Thêm health check vào các instance
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.withCaching()
.build(context);
}
}// Load balancer có trọng số
@Component
@RequiredArgsConstructor
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final String serviceId;
private final ObjectProvider<ServiceInstanceListSupplier> supplierProvider;
private final Random random = new Random();
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplierProvider.getIfAvailable()
.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// Tính trọng số từ metadata
List<WeightedInstance> weighted = instances.stream()
.map(instance -> {
int weight = Integer.parseInt(
instance.getMetadata().getOrDefault("weight", "1")
);
return new WeightedInstance(instance, weight);
})
.toList();
// Lựa chọn theo trọng số
int totalWeight = weighted.stream()
.mapToInt(WeightedInstance::weight)
.sum();
int randomWeight = random.nextInt(totalWeight);
int currentWeight = 0;
for (WeightedInstance wi : weighted) {
currentWeight += wi.weight();
if (randomWeight < currentWeight) {
return new DefaultResponse(wi.instance());
}
}
return new DefaultResponse(weighted.get(0).instance());
});
}
private record WeightedInstance(ServiceInstance instance, int weight) {}
}Câu hỏi 8: Triển khai circuit breaker trong gateway như thế nào?
Circuit breaker bảo vệ chống các sự cố lan truyền. Spring Cloud Gateway tích hợp với Resilience4j để quản lý sự cố nâng cao.
# application.yml
# Cấu hình Circuit Breaker với Resilience4j
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
# Circuit breaker với fallback
- name: CircuitBreaker
args:
name: orderServiceCB
fallbackUri: forward:/fallback/orders
resilience4j:
circuitbreaker:
configs:
default:
# Số yêu cầu được đánh giá
slidingWindowSize: 10
# Ngưỡng lỗi để mở mạch
failureRateThreshold: 50
# Khoảng thời gian mạch ở trạng thái mở trước khi thử lại
waitDurationInOpenState: 30s
# Số yêu cầu cho phép trong half-open
permittedNumberOfCallsInHalfOpenState: 3
# Chuyển trạng thái tự động
automaticTransitionFromOpenToHalfOpenEnabled: true
instances:
orderServiceCB:
baseConfig: default
failureRateThreshold: 60
timelimiter:
configs:
default:
timeoutDuration: 5s
instances:
orderServiceCB:
timeoutDuration: 3s// Controller fallback
@RestController
@RequestMapping("/fallback")
@Slf4j
public class FallbackController {
@GetMapping("/orders")
public Mono<ResponseEntity<Map<String, Object>>> ordersFallback(
ServerWebExchange exchange
) {
log.warn("Circuit breaker activated for orders service");
Map<String, Object> response = Map.of(
"success", false,
"error", "Service temporarily unavailable",
"code", "SERVICE_UNAVAILABLE",
"retryAfter", 30
);
return Mono.just(ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(response));
}
@PostMapping("/orders")
public Mono<ResponseEntity<Map<String, Object>>> ordersPostFallback() {
Map<String, Object> response = Map.of(
"success", false,
"error", "Order creation temporarily unavailable",
"code", "SERVICE_UNAVAILABLE",
"message", "Please try again later"
);
return Mono.just(ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(response));
}
}// Theo dõi sự kiện của circuit breaker
@Component
@Slf4j
@RequiredArgsConstructor
public class CircuitBreakerEventListener {
private final MeterRegistry meterRegistry;
@EventListener
public void onCircuitBreakerStateTransition(
CircuitBreakerOnStateTransitionEvent event
) {
CircuitBreaker.StateTransition transition = event.getStateTransition();
log.info("Circuit breaker {} state changed: {} -> {}",
event.getCircuitBreakerName(),
transition.getFromState(),
transition.getToState());
// Metric Micrometer
meterRegistry.counter(
"circuit_breaker.state_transition",
"name", event.getCircuitBreakerName(),
"from", transition.getFromState().name(),
"to", transition.getToState().name()
).increment();
}
@EventListener
public void onCircuitBreakerFailure(CircuitBreakerOnErrorEvent event) {
log.error("Circuit breaker {} error: {}",
event.getCircuitBreakerName(),
event.getThrowable().getMessage());
}
}Cấu hình timeout nhất quán giữa circuit breaker và HTTP client. Timeout quá dài làm chặn thread, còn quá ngắn sẽ gây cảnh báo sai.
Câu hỏi 9: Triển khai retry với backoff theo cấp số nhân thế nào?
Một retry thông minh với backoff theo cấp số nhân giúp tránh quá tải dịch vụ đang gặp khó khăn, đồng thời tối đa hóa khả năng thành công.
# application.yml
# Cấu hình retry với backoff
spring:
cloud:
gateway:
routes:
- id: payment-service
uri: lb://payment-service
predicates:
- Path=/api/payments/**
filters:
- name: Retry
args:
# Số lần thử
retries: 3
# Mã HTTP kích hoạt retry
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE,GATEWAY_TIMEOUT
# Chỉ phương thức HTTP idempotent
methods: GET,PUT
# Ngoại lệ kích hoạt retry
exceptions:
- java.io.IOException
- java.net.ConnectException
- org.springframework.cloud.gateway.support.TimeoutException
# Backoff theo cấp số nhân
backoff:
firstBackoff: 100ms
maxBackoff: 2000ms
factor: 2
basedOnPreviousValue: true// Retry tùy chỉnh với logic nghiệp vụ
@Component
@Slf4j
public class CustomRetryFilter implements GatewayFilter, Ordered {
private static final int MAX_RETRIES = 3;
private static final Duration INITIAL_BACKOFF = Duration.ofMillis(100);
private static final double BACKOFF_MULTIPLIER = 2.0;
private static final double JITTER_FACTOR = 0.1;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return Mono.defer(() -> attemptRequest(exchange, chain, 0));
}
private Mono<Void> attemptRequest(
ServerWebExchange exchange,
GatewayFilterChain chain,
int attempt
) {
return chain.filter(exchange)
.onErrorResume(throwable -> {
if (attempt >= MAX_RETRIES || !isRetryable(throwable, exchange)) {
return Mono.error(throwable);
}
Duration backoff = calculateBackoff(attempt);
log.warn("Retry attempt {} after {}ms for {} {}",
attempt + 1,
backoff.toMillis(),
exchange.getRequest().getMethod(),
exchange.getRequest().getPath());
return Mono.delay(backoff)
.then(Mono.defer(() ->
attemptRequest(exchange, chain, attempt + 1)
));
});
}
private boolean isRetryable(Throwable throwable, ServerWebExchange exchange) {
// Chỉ thử lại các phương thức idempotent
HttpMethod method = exchange.getRequest().getMethod();
if (!Set.of(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE)
.contains(method)) {
return false;
}
// Kiểm tra loại ngoại lệ
return throwable instanceof ConnectException ||
throwable instanceof TimeoutException ||
throwable instanceof ServiceUnavailableException;
}
private Duration calculateBackoff(int attempt) {
// Backoff theo cấp số nhân kèm jitter
long baseBackoff = (long) (
INITIAL_BACKOFF.toMillis() * Math.pow(BACKOFF_MULTIPLIER, attempt)
);
// Thêm jitter ngẫu nhiên (±10%)
double jitter = 1.0 + (Math.random() - 0.5) * 2 * JITTER_FACTOR;
long finalBackoff = (long) (baseBackoff * jitter);
return Duration.ofMillis(finalBackoff);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
}
}Mẫu nâng cao và thực hành tốt nhất
Câu hỏi 10: Triển khai aggregation yêu cầu như thế nào?
Aggregation kết hợp nhiều cuộc gọi microservices thành một phản hồi duy nhất cho client, giảm độ trễ và độ phức tạp ở frontend.
// Aggregation đa dịch vụ
@RestController
@RequestMapping("/api/aggregate")
@RequiredArgsConstructor
@Slf4j
public class AggregationController {
private final WebClient.Builder webClientBuilder;
private final CircuitBreakerRegistry circuitBreakerRegistry;
@GetMapping("/user-dashboard/{userId}")
public Mono<DashboardResponse> getUserDashboard(@PathVariable Long userId) {
// Gọi song song tới các dịch vụ khác nhau
Mono<UserProfile> userMono = fetchUserProfile(userId);
Mono<List<Order>> ordersMono = fetchRecentOrders(userId);
Mono<NotificationCount> notificationsMono = fetchNotificationCount(userId);
// Tổng hợp kết quả
return Mono.zip(userMono, ordersMono, notificationsMono)
.map(tuple -> DashboardResponse.builder()
.user(tuple.getT1())
.recentOrders(tuple.getT2())
.notificationCount(tuple.getT3())
.generatedAt(Instant.now())
.build())
.timeout(Duration.ofSeconds(5))
.onErrorResume(this::handleAggregationError);
}
private Mono<UserProfile> fetchUserProfile(Long userId) {
return webClientBuilder.build()
.get()
.uri("lb://user-service/users/{id}", userId)
.retrieve()
.bodyToMono(UserProfile.class)
.transform(CircuitBreakerOperator.of(
circuitBreakerRegistry.circuitBreaker("user-service")
))
.onErrorReturn(new UserProfile(userId, "Unknown", null));
}
private Mono<List<Order>> fetchRecentOrders(Long userId) {
return webClientBuilder.build()
.get()
.uri("lb://order-service/orders?userId={id}&limit=5", userId)
.retrieve()
.bodyToFlux(Order.class)
.collectList()
.transform(CircuitBreakerOperator.of(
circuitBreakerRegistry.circuitBreaker("order-service")
))
.onErrorReturn(Collections.emptyList());
}
private Mono<NotificationCount> fetchNotificationCount(Long userId) {
return webClientBuilder.build()
.get()
.uri("lb://notification-service/notifications/count/{id}", userId)
.retrieve()
.bodyToMono(NotificationCount.class)
.transform(CircuitBreakerOperator.of(
circuitBreakerRegistry.circuitBreaker("notification-service")
))
.onErrorReturn(new NotificationCount(0, 0));
}
private Mono<DashboardResponse> handleAggregationError(Throwable error) {
log.error("Dashboard aggregation failed: {}", error.getMessage());
return Mono.just(DashboardResponse.builder()
.error("Partial data available")
.generatedAt(Instant.now())
.build());
}
}// DTO cho phản hồi đã tổng hợp
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DashboardResponse {
private UserProfile user;
private List<Order> recentOrders;
private NotificationCount notificationCount;
private Instant generatedAt;
private String error;
}Câu hỏi 11: Bảo mật gateway với OAuth2 như thế nào?
Tích hợp OAuth2 tập trung xác thực ở cấp gateway, tránh trùng lặp logic ở mỗi microservice.
# application.yml
# Cấu hình OAuth2 Resource Server
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com/realms/myrealm
jwk-set-uri: https://auth.example.com/realms/myrealm/protocol/openid-connect/certs// Cấu hình bảo mật của gateway
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
// Tắt CSRF cho API stateless
.csrf(ServerHttpSecurity.CsrfSpec::disable)
// Cấu hình ủy quyền
.authorizeExchange(exchanges -> exchanges
// Endpoint công khai
.pathMatchers("/actuator/health", "/actuator/info").permitAll()
.pathMatchers("/api/public/**").permitAll()
.pathMatchers("/api/auth/**").permitAll()
// Endpoint theo vai trò
.pathMatchers("/api/admin/**").hasRole("ADMIN")
.pathMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
// Phần còn lại cần xác thực
.anyExchange().authenticated()
)
// OAuth2 Resource Server với JWT
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
)
.build();
}
@Bean
public ReactiveJwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
new JwtGrantedAuthoritiesConverter();
// Trích xuất vai trò từ claim "roles"
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
ReactiveJwtAuthenticationConverter converter =
new ReactiveJwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(
new ReactiveJwtGrantedAuthoritiesConverterAdapter(grantedAuthoritiesConverter)
);
return converter;
}
}// Chuyển token tới các dịch vụ downstream
@Component
public class TokenRelayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(auth -> auth instanceof JwtAuthenticationToken)
.cast(JwtAuthenticationToken.class)
.map(JwtAuthenticationToken::getToken)
.map(jwt -> {
// Chuyển token tới các dịch vụ downstream
ServerHttpRequest request = exchange.getRequest()
.mutate()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + jwt.getTokenValue())
// Thêm thông tin người dùng được trích từ token
.header("X-User-Id", jwt.getSubject())
.header("X-User-Email", jwt.getClaimAsString("email"))
.build();
return exchange.mutate().request(request).build();
})
.defaultIfEmpty(exchange)
.flatMap(chain::filter);
}
@Override
public int getOrder() {
// Sau xác thực, trước routing
return SecurityWebFiltersOrder.AUTHENTICATION.getOrder() + 1;
}
}Sẵn sàng chinh phục phỏng vấn Spring Boot?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Câu hỏi 12: Đâu là thực hành tốt nhất cho monitoring và observability?
Giám sát gateway rất quan trọng để phát hiện vấn đề về hiệu năng và tính khả dụng trong kiến trúc microservices.
# application.yml
# Cấu hình observability
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,gateway
metrics:
tags:
application: api-gateway
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5, 0.95, 0.99
tracing:
sampling:
probability: 1.0
spring:
cloud:
gateway:
metrics:
enabled: true
tags:
path:
enabled: true// Filter metric tùy chỉnh
@Component
@RequiredArgsConstructor
@Slf4j
public class MetricsFilter implements GlobalFilter, Ordered {
private final MeterRegistry meterRegistry;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long startTime = System.nanoTime();
String path = exchange.getRequest().getPath().value();
String method = exchange.getRequest().getMethod().name();
// Lấy dịch vụ đích từ route
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
String routeId = route != null ? route.getId() : "unknown";
return chain.filter(exchange)
.doOnSuccess(v -> recordMetrics(exchange, startTime, routeId, "success"))
.doOnError(e -> recordMetrics(exchange, startTime, routeId, "error"));
}
private void recordMetrics(
ServerWebExchange exchange,
long startTime,
String routeId,
String outcome
) {
long duration = System.nanoTime() - startTime;
HttpStatusCode status = exchange.getResponse().getStatusCode();
String statusCode = status != null ? String.valueOf(status.value()) : "0";
// Timer cho độ trễ
Timer.builder("gateway.request.duration")
.tag("route", routeId)
.tag("method", exchange.getRequest().getMethod().name())
.tag("status", statusCode)
.tag("outcome", outcome)
.register(meterRegistry)
.record(duration, TimeUnit.NANOSECONDS);
// Bộ đếm yêu cầu
meterRegistry.counter(
"gateway.requests.total",
"route", routeId,
"status", statusCode
).increment();
// Log các yêu cầu chậm
if (duration > TimeUnit.SECONDS.toNanos(1)) {
log.warn("Slow request: {} {} - {}ms (route: {})",
exchange.getRequest().getMethod(),
exchange.getRequest().getPath(),
TimeUnit.NANOSECONDS.toMillis(duration),
routeId);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}// Truyền ngữ cảnh tracing
@Component
@RequiredArgsConstructor
public class TracingFilter implements GlobalFilter, Ordered {
private final Tracer tracer;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Tạo hoặc lấy span tracing
Span span = tracer.nextSpan()
.name("gateway-request")
.tag("http.method", exchange.getRequest().getMethod().name())
.tag("http.url", exchange.getRequest().getURI().toString())
.start();
// Chèn header tracing
ServerHttpRequest request = exchange.getRequest()
.mutate()
.header("X-Trace-Id", span.context().traceId())
.header("X-Span-Id", span.context().spanId())
.build();
return chain.filter(exchange.mutate().request(request).build())
.doOnSuccess(v -> {
HttpStatusCode status = exchange.getResponse().getStatusCode();
span.tag("http.status_code",
status != null ? String.valueOf(status.value()) : "0");
span.end();
})
.doOnError(e -> {
span.tag("error", e.getMessage());
span.end();
});
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}Bảng các metric cốt lõi:
| Metric | Mô tả |
|-----------------------------------|----------------------------------|
| gateway.request.duration | Độ trễ theo route/trạng thái |
| gateway.requests.total | Bộ đếm yêu cầu |
| resilience4j.circuitbreaker.state | Trạng thái circuit breaker |
| http.server.requests | Metric HTTP tiêu chuẩn |
| spring.cloud.gateway.routes | Route đang hoạt động |Sử dụng Grafana với dashboard của Spring Cloud Gateway và Resilience4j để trực quan hóa metric. Cấu hình cảnh báo cho độ trễ P99 và tỷ lệ lỗi.
Kết luận
Spring Cloud Gateway là thành phần thiết yếu của các kiến trúc microservices hiện đại. Những điểm then chốt cần ghi nhớ cho phỏng vấn:
Kiến trúc và khái niệm:
- ✅ Routes, Predicates và Filters tạo nên mô hình nền tảng
- ✅ Kiến trúc phản ứng với WebFlux và Netty
- ✅ Tích hợp gốc với hệ sinh thái Spring Cloud
Tính năng cốt lõi:
- ✅ Routing động dựa trên nhiều tiêu chí
- ✅ Filter pre/post để biến đổi yêu cầu và phản hồi
- ✅ Load balancing với Spring Cloud LoadBalancer
Khả năng phục hồi và bảo mật:
- ✅ Circuit breaker với Resilience4j và fallback
- ✅ Retry với backoff theo cấp số nhân và jitter
- ✅ Xác thực OAuth2/JWT tập trung
Observability:
- ✅ Metric Micrometer với tag theo route
- ✅ Tracing phân tán với truyền ngữ cảnh
- ✅ Health check và endpoint actuator
Thành thạo Spring Cloud Gateway thể hiện sự hiểu biết sâu sắc về các mẫu microservices và bài toán mở rộng quy mô. Đây là những năng lực cần thiết để thiết kế các API Gateway bền bỉ và hiệu năng cao.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

Spring Kafka: kiến trúc event-driven với consumer chịu lỗi
Hướng dẫn đầy đủ về Spring Kafka cho kiến trúc event-driven. Cấu hình, consumer chịu lỗi, chính sách retry, dead letter queue và các mẫu sản xuất cho ứng dụng phân tán.

Logging Spring Boot năm 2026: log có cấu trúc trên production với Logback và JSON
Hướng dẫn đầy đủ về logging có cấu trúc trong Spring Boot. Cấu hình Logback JSON, MDC cho tracing, các best practice production và tích hợp ELK Stack.

Phỏng vấn Spring GraphQL: Resolver, DataLoader và Giải pháp cho Vấn đề N+1
Chuẩn bị cho phỏng vấn Spring GraphQL với hướng dẫn đầy đủ này. Resolver, DataLoader, xử lý vấn đề N+1, mutation và các thực hành tốt nhất cho câu hỏi kỹ thuật.