<返回目录     Powered by claud/xia兄

第7课: 服务网关

Spring Cloud Gateway、Zuul 统一入口与路由

API 网关的核心价值

在微服务架构中,API 网关作为系统的统一入口,提供以下核心功能:

Spring Cloud Gateway 核心概念

Route(路由):网关的基本构建块,包含 ID、目标 URI、断言集合和过滤器集合。

Predicate(断言):匹配 HTTP 请求的条件,如路径、方法、请求头等。

Filter(过滤器):在请求前后执行的逻辑,如修改请求头、限流、日志记录等。

Spring Cloud Gateway 实战

1. 基础配置

// pom.xml 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

// 启动类
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

# application.yml 基础配置
server:
  port: 8080

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        # 用户服务路由
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
        
        # 订单服务路由
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1

2. 路由断言(Predicate)

# 多种断言方式
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            # 路径匹配
            - Path=/api/users/**
            # 方法匹配
            - Method=GET,POST
            # 请求头匹配
            - Header=X-Request-Id, \d+
            # 查询参数匹配
            - Query=token
            # Cookie 匹配
            - Cookie=session, abc.*
            # Host 匹配
            - Host=**.example.com
            # 时间匹配(在指定时间后生效)
            - After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
            # 权重路由(灰度发布)
            - Weight=group1, 8

3. 内置过滤器

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            # 去除路径前缀
            - StripPrefix=1
            # 添加请求头
            - AddRequestHeader=X-Request-Source, gateway
            # 添加响应头
            - AddResponseHeader=X-Response-Time, ${timestamp}
            # 添加请求参数
            - AddRequestParameter=source, gateway
            # 重定向
            - RedirectTo=302, https://example.com
            # 重写路径
            - RewritePath=/api/(?<segment>.*), /$\{segment}
            # 限流
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
            # 熔断
            - name: CircuitBreaker
              args:
                name: userServiceCircuitBreaker
                fallbackUri: forward:/fallback/users
            # 重试
            - name: Retry
              args:
                retries: 3
                statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
                methods: GET,POST
                backoff:
                  firstBackoff: 10ms
                  maxBackoff: 50ms
                  factor: 2

4. 自定义全局过滤器

@Component
@Order(-1)  // 优先级,数字越小优先级越高
public class AuthGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 获取 Token
        String token = request.getHeaders().getFirst("Authorization");

        // 白名单路径
        String path = request.getPath().value();
        if (path.startsWith("/api/public/")) {
            return chain.filter(exchange);
        }

        // 验证 Token
        if (token == null || !validateToken(token)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        // 添加用户信息到请求头
        String userId = extractUserId(token);
        ServerHttpRequest mutatedRequest = request.mutate()
            .header("X-User-Id", userId)
            .build();

        ServerWebExchange mutatedExchange = exchange.mutate()
            .request(mutatedRequest)
            .build();

        return chain.filter(mutatedExchange);
    }

    private boolean validateToken(String token) {
        // Token 验证逻辑
        return true;
    }

    private String extractUserId(String token) {
        // 提取用户 ID
        return "123";
    }
}

5. 自定义路由过滤器

@Component
public class LoggingGatewayFilterFactory 
    extends AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> {

    public LoggingGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            
            // 请求前日志
            if (config.isPreLogger()) {
                System.out.println("请求路径: " + request.getPath());
                System.out.println("请求方法: " + request.getMethod());
            }

            // 记录开始时间
            exchange.getAttributes().put("startTime", System.currentTimeMillis());

            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                // 响应后日志
                if (config.isPostLogger()) {
                    Long startTime = exchange.getAttribute("startTime");
                    Long duration = System.currentTimeMillis() - startTime;
                    System.out.println("响应状态: " + exchange.getResponse().getStatusCode());
                    System.out.println("响应时间: " + duration + "ms");
                }
            }));
        };
    }

    public static class Config {
        private boolean preLogger = true;
        private boolean postLogger = true;

        // Getters and Setters
        public boolean isPreLogger() { return preLogger; }
        public void setPreLogger(boolean preLogger) { this.preLogger = preLogger; }
        public boolean isPostLogger() { return postLogger; }
        public void setPostLogger(boolean postLogger) { this.postLogger = postLogger; }
    }
}

# 使用自定义过滤器
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: Logging
              args:
                preLogger: true
                postLogger: true

6. 限流配置

// pom.xml 添加 Redis 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

# application.yml 配置
spring:
  redis:
    host: localhost
    port: 6379
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒填充速率
                redis-rate-limiter.replenishRate: 10
                # 令牌桶容量
                redis-rate-limiter.burstCapacity: 20
                # 限流 Key 解析器
                key-resolver: "#{@ipKeyResolver}"

// 自定义限流 Key 解析器
@Bean
public KeyResolver ipKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
    );
}

@Bean
public KeyResolver userKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getHeaders().getFirst("X-User-Id")
    );
}

@Bean
public KeyResolver apiKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getPath().value()
    );
}

7. 熔断降级

// pom.xml 添加 Resilience4j 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>

# application.yml 配置
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: CircuitBreaker
              args:
                name: userServiceCircuitBreaker
                fallbackUri: forward:/fallback/users

resilience4j:
  circuitbreaker:
    instances:
      userServiceCircuitBreaker:
        # 滑动窗口大小
        slidingWindowSize: 10
        # 失败率阈值
        failureRateThreshold: 50
        # 等待时间
        waitDurationInOpenState: 10000
        # 半开状态允许的调用数
        permittedNumberOfCallsInHalfOpenState: 3

// 降级处理器
@RestController
public class FallbackController {

    @GetMapping("/fallback/users")
    public Mono<Map<String, Object>> userFallback() {
        Map<String, Object> result = new HashMap<>();
        result.put("code", 503);
        result.put("message", "用户服务暂时不可用,请稍后重试");
        return Mono.just(result);
    }

    @GetMapping("/fallback/orders")
    public Mono<Map<String, Object>> orderFallback() {
        Map<String, Object> result = new HashMap<>();
        result.put("code", 503);
        result.put("message", "订单服务暂时不可用,请稍后重试");
        return Mono.just(result);
    }
}

8. 跨域配置

# application.yml 配置
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowedHeaders: "*"
            allowCredentials: true
            maxAge: 3600

// 或使用配置类
@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

9. 动态路由

@Service
public class DynamicRouteService {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    @Autowired
    private ApplicationEventPublisher publisher;

    // 添加路由
    public String addRoute(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }

    // 删除路由
    public String deleteRoute(String id) {
        routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }

    // 更新路由
    public String updateRoute(RouteDefinition definition) {
        deleteRoute(definition.getId());
        addRoute(definition);
        return "success";
    }
}

@RestController
@RequestMapping("/route")
public class RouteController {

    @Autowired
    private DynamicRouteService dynamicRouteService;

    @PostMapping("/add")
    public String addRoute(@RequestBody RouteDefinition definition) {
        return dynamicRouteService.addRoute(definition);
    }

    @DeleteMapping("/delete/{id}")
    public String deleteRoute(@PathVariable String id) {
        return dynamicRouteService.deleteRoute(id);
    }

    @PutMapping("/update")
    public String updateRoute(@RequestBody RouteDefinition definition) {
        return dynamicRouteService.updateRoute(definition);
    }
}

10. 网关监控

// pom.xml 添加 Actuator 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

# application.yml 配置
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    gateway:
      enabled: true

# 访问网关端点
GET /actuator/gateway/routes          # 查看所有路由
GET /actuator/gateway/routes/{id}     # 查看指定路由
POST /actuator/gateway/refresh        # 刷新路由
GET /actuator/gateway/globalfilters   # 查看全局过滤器
GET /actuator/gateway/routefilters    # 查看路由过滤器
最佳实践:

实践练习

练习任务:
  1. 搭建网关:创建 Gateway 项目,配置基础路由
  2. 认证过滤器:实现全局认证过滤器,验证 JWT Token
  3. 限流功能:配置 Redis 限流,测试不同限流策略
  4. 熔断降级:集成 Resilience4j,实现服务熔断和降级
  5. 日志记录:自定义过滤器记录请求响应日志
  6. 动态路由:实现路由的动态增删改查
  7. 灰度发布:使用权重路由实现灰度发布
  8. 性能测试:使用 JMeter 测试网关的性能和限流效果

常见问题

Q: Gateway 和 Zuul 如何选择?

A: Gateway 基于 WebFlux 响应式编程,性能更好,是官方推荐方案。Zuul 1.x 基于 Servlet,已停止维护。

Q: 网关如何处理超时?

A: 配置 connect-timeout 和 response-timeout,结合熔断降级保护系统。建议超时时间设置为 5-10 秒。

总结

API 网关是微服务的统一入口。通过本课学习,你应该掌握: