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: câu hỏi phỏng vấn về routing, filter và load balancing

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.

GatewayApplication.javajava
// 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.

RouteConfiguration.javajava
// 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:

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
CustomPredicateFactory.javajava
// 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

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.

LoggingFilter.javajava
// 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;
    }
}
AuthenticationFilter.javajava
// 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.

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/(?<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}"
RateLimiterConfiguration.javajava
// 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.

RequestBodyModificationFilter.javajava
// 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;
    }
}
ResponseBodyModificationConfig.javajava
// 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/
LoadBalancerConfiguration.javajava
// 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);
    }
}
WeightedLoadBalancer.javajava
// 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.

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
FallbackController.javajava
// 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));
    }
}
CircuitBreakerEventListener.javajava
// 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
CustomRetryFilter.javajava
// 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.

AggregationController.javajava
// 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());
    }
}
DashboardResponse.javajava
// 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.

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
SecurityConfig.javajava
// 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;
    }
}
TokenRelayFilter.javajava
// 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.

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
MetricsFilter.javajava
// 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;
    }
}
TracingFilter.javajava
// 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:

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.

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ẻ

#spring cloud gateway
#microservices
#api gateway
#routing
#technical interview

Chia sẻ

Bài viết liên quan