# 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ã. - Published: 2026-03-20 - Updated: 2026-05-01 - Author: SharpSkill - Tags: spring cloud gateway, microservices, api gateway, routing, technical interview - Reading time: 15 min --- 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ả. > **Mẹo chuẩn bị phỏng vấn** > > 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. ```java // GatewayApplication.java // 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); } } ``` ```yaml # 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. ```java // RouteConfiguration.java // 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/(?.*)", "/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: ```text 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 client ``` ### Câ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. ```yaml # 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 ``` ```java // CustomPredicateFactory.java // Tạo predicate tùy chỉnh @Component public class ApiKeyRoutePredicateFactory extends AbstractRoutePredicateFactory { public ApiKeyRoutePredicateFactory() { super(Config.class); } @Override public Predicate 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 validKeys = new ArrayList<>(); public List getValidKeys() { return validKeys; } public void setValidKeys(List validKeys) { this.validKeys = validKeys; } } } ``` > **Thứ tự predicate** > > 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. ```java // LoggingFilter.java // Filter logging toàn cục @Component @Slf4j public class LoggingFilter implements GlobalFilter, Ordered { @Override public Mono 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; } } ``` ```java // AuthenticationFilter.java // Filter xác thực JWT @Component @RequiredArgsConstructor public class AuthenticationFilter implements GatewayFilter { private final JwtTokenValidator tokenValidator; @Override public Mono 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 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. ```yaml # 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/(?.*), /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}" ``` ```java // RateLimiterConfiguration.java // 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() ); } } ``` ### 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. ```java // RequestBodyModificationFilter.java // 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 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 body = objectMapper.readValue( bytes, new TypeReference>() {} ); // 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 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; } } ``` ```java // ResponseBodyModificationConfig.java // 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. ```yaml # 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/ ``` ```java // LoadBalancerConfiguration.java // 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 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); } } ``` ```java // WeightedLoadBalancer.java // Load balancer có trọng số @Component @RequiredArgsConstructor public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer { private final String serviceId; private final ObjectProvider supplierProvider; private final Random random = new Random(); @Override public Mono> choose(Request request) { return supplierProvider.getIfAvailable() .get() .next() .map(instances -> { if (instances.isEmpty()) { return new EmptyResponse(); } // Tính trọng số từ metadata List 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. ```yaml # 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 ``` ```java // FallbackController.java // Controller fallback @RestController @RequestMapping("/fallback") @Slf4j public class FallbackController { @GetMapping("/orders") public Mono>> ordersFallback( ServerWebExchange exchange ) { log.warn("Circuit breaker activated for orders service"); Map 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>> ordersPostFallback() { Map 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)); } } ``` ```java // CircuitBreakerEventListener.java // 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()); } } ``` > **Timeout và circuit breaker** > > 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. ```yaml # 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 ``` ```java // CustomRetryFilter.java // 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 filter(ServerWebExchange exchange, GatewayFilterChain chain) { return Mono.defer(() -> attemptRequest(exchange, chain, 0)); } private Mono 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. ```java // AggregationController.java // 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 getUserDashboard(@PathVariable Long userId) { // Gọi song song tới các dịch vụ khác nhau Mono userMono = fetchUserProfile(userId); Mono> ordersMono = fetchRecentOrders(userId); Mono 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 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> 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 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 handleAggregationError(Throwable error) { log.error("Dashboard aggregation failed: {}", error.getMessage()); return Mono.just(DashboardResponse.builder() .error("Partial data available") .generatedAt(Instant.now()) .build()); } } ``` ```java // DashboardResponse.java // DTO cho phản hồi đã tổng hợp @Data @Builder @NoArgsConstructor @AllArgsConstructor public class DashboardResponse { private UserProfile user; private List 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. ```yaml # 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 ``` ```java // SecurityConfig.java // 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; } } ``` ```java // TokenRelayFilter.java // Chuyển token tới các dịch vụ downstream @Component public class TokenRelayFilter implements GlobalFilter, Ordered { @Override public Mono 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; } } ``` ### 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. ```yaml # 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 ``` ```java // MetricsFilter.java // Filter metric tùy chỉnh @Component @RequiredArgsConstructor @Slf4j public class MetricsFilter implements GlobalFilter, Ordered { private final MeterRegistry meterRegistry; @Override public Mono 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; } } ``` ```java // TracingFilter.java // Truyền ngữ cảnh tracing @Component @RequiredArgsConstructor public class TracingFilter implements GlobalFilter, Ordered { private final Tracer tracer; @Override public Mono 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: ```text | 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 | ``` > **Dashboard khuyến nghị** > > 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. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/spring-boot/spring-cloud-gateway-interview-routing-filters