Skip to content

网关 Gateway 的使用详解

发表: at 13:30

引言

在微服务架构中,网关(Gateway)作为系统的入口,扮演着至关重要的角色。它不仅是请求的第一道屏障,也是服务路由、负载均衡、认证授权等功能的集中处理点。本文将详细介绍网关的概念、主要功能及其在实际项目中的应用,并通过代码示例帮助大家更好地理解和使用网关。

图片

一、网关的概念与作用

网关是微服务架构中的核心组件,位于客户端和微服务之间。它接收所有客户端的请求,然后将请求路由到相应的微服务。网关不仅仅是一个简单的路由转发工具,它还承担着许多关键功能:

  1. 路由转发:根据请求的URL将请求转发到相应的微服务
  2. 负载均衡:分散请求流量,确保系统的稳定性
  3. 安全控制:提供认证、授权等安全机制
  4. 协议转换:支持不同协议之间的转换
  5. 限流熔断:保护系统免受突发流量的影响
  6. 日志监控:记录请求信息,便于系统监控和问题排查

二、主流网关技术

目前市场上有多种网关实现技术,以下是几种主流的网关框架:

1. Spring Cloud Gateway

Spring Cloud Gateway是Spring Cloud生态系统中的网关组件,基于Spring 5、Spring Boot 2和Project Reactor构建,提供了一种简单而有效的方式来路由到API,并为它们提供跨领域的关注点,如安全性、监控/指标和弹性。

2. Netflix Zuul

Zuul是Netflix开源的网关服务,基于Servlet架构构建,是Spring Cloud中的一个重要组件。虽然目前已经发布了Zuul 2,但Spring Cloud暂时还没有集成。

3. Kong

Kong是一个云原生、快速、可扩展且分布式的微服务抽象层(也称为API网关或API中间件)。

4. Nginx+Lua

基于Nginx和Lua脚本的网关解决方案,性能卓越,配置灵活。

三、Spring Cloud Gateway详解

下面我们以Spring Cloud Gateway为例,详细介绍网关的使用方法:

1. 基本配置

首先,添加Spring Cloud Gateway依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

2. 路由配置

在application.yml中配置路由规则:

spring:
  cloud:
    gateway:
      routes:
        # 定义一个名为route-user的路由
        -id:route-user
          # 设置路由的目标URI,这里指向用户服务
          uri:lb://user-service
          # 定义路由断言,满足条件的请求会被路由到目标URI
          predicates:
            # 匹配路径前缀为/user的请求
            -Path=/user/**
          # 定义过滤器,用于处理请求或响应
          filters:
            # 去除路径前缀,数字1表示去除第一级路径
            -StripPrefix=1
            # 添加请求头
            -AddRequestHeader=X-Request-Id,${random.uuid}
        
        # 定义一个名为route-order的路由
        -id:route-order
          uri:lb://order-service
          predicates:
            -Path=/order/**
          filters:
            -StripPrefix=1

3. 编程式配置

除了使用配置文件,我们还可以通过Java代码进行路由配置:

@Configuration
public class GatewayConfig {
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            // 创建一个路由规则
            .route("route-user", r -> r
                // 匹配路径前缀为/user的请求
                .path("/user/**")
                // 过滤器:去除路径前缀
                .filters(f -> f.stripPrefix(1)
                    // 添加请求头
                    .addRequestHeader("X-Request-Id", UUID.randomUUID().toString()))
                // 设置路由目标为user-service服务,使用负载均衡
                .uri("lb://user-service"))
            
            .route("route-order", r -> r
                .path("/order/**")
                .filters(f -> f.stripPrefix(1))
                .uri("lb://order-service"))
            
            .build();
    }
}

四、网关跨域配置

在前后端分离的架构中,跨域问题是常见的挑战之一。网关作为系统的统一入口,是处理跨域问题的理想位置。Spring Cloud Gateway提供了多种配置跨域的方式:

1. 配置文件方式

在application.yml中添加CORS配置:

spring:
  cloud:
    gateway:
      # 全局CORS配置
      globalcors:
        corsConfigurations:
          '[/**]':# 匹配所有路径
            # 允许的请求源,设置为*表示允许所有源
            allowedOrigins:"*"
            # 允许的请求方法
            allowedMethods:
              -GET
              -POST
              -PUT
              -DELETE
              -OPTIONS
            # 允许的请求头
            allowedHeaders:"*"
            # 是否允许发送cookie
            allowCredentials:true
            # 预检请求的有效期,单位为秒
            maxAge:3600

2. 编程式配置

通过Java代码配置CORS:

@Configuration
public class CorsConfig {
    
    @Bean
    public CorsWebFilter corsWebFilter() {
        // 创建CORS配置源
        CorsConfiguration config = new CorsConfiguration();
        
        // 允许的请求源,设置为*表示允许所有源
        config.addAllowedOrigin("*");
        // 或者设置具体的允许源
        // config.addAllowedOrigin("http://example.com");
        // config.addAllowedOrigin("https://example.org");
        
        // 允许的请求头
        config.addAllowedHeader("*");
        
        // 允许的请求方法
        config.addAllowedMethod("*");
        
        // 是否允许发送cookie
        config.setAllowCredentials(true);
        
        // 预检请求的有效期,单位为秒
        config.setMaxAge(3600L);
        
        // 创建URL匹配器,匹配所有路径
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        
        // 创建并返回CORS过滤器
        returnnew CorsWebFilter(source);
    }
}

3. 单个路由的CORS配置

如果需要为不同的路由配置不同的CORS策略,可以在路由配置中单独设置:

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("route-with-cors", r -> r
            .path("/api/**")
            .filters(f -> f
                // 添加CORS预检响应
                .filter((exchange, chain) -> {
                    ServerHttpRequest request = exchange.getRequest();
                    // 处理预检请求
                    if (HttpMethod.OPTIONS.equals(request.getMethod())) {
                        ServerHttpResponse response = exchange.getResponse();
                        HttpHeaders headers = response.getHeaders();
                        
                        // 设置CORS响应头
                        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
                        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,PUT,DELETE,OPTIONS");
                        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
                        headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
                        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                        
                        // 返回200 OK状态
                        response.setStatusCode(HttpStatus.OK);
                        return Mono.empty(); // 对于OPTIONS请求,直接返回响应,不继续传递请求
                    }
                    
                    // 非OPTIONS请求继续执行过滤器链
                    return chain.filter(exchange);
                }))
            .uri("lb://api-service"))
        .build();
}

4. 跨域最佳实践

在配置跨域时,需要注意以下几点:

  1. 安全性考虑:不要无条件地允许所有源,尽可能限制为特定的可信源。
  2. 只允许必要的方法:根据API的实际需求,只允许必要的HTTP方法。
  3. 合理设置maxAge:设置合理的预检请求缓存时间,减少OPTIONS请求次数。
  4. 谨慎处理Cookie:只有在必要时才设置allowCredentials为true,并确保不设置allowedOrigin为”*“,而是设置为具体的域名。
  5. 考虑生产环境的差异:不同环境可能需要不同的CORS配置,可以通过配置文件区分。
// 根据环境配置不同的CORS策略
@Configuration
@Profile("prod") // 只在生产环境生效
public class ProdCorsConfig {
    
    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // 生产环境只允许特定域名
        config.addAllowedOrigin("https://www.example.com");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setAllowCredentials(true);
        config.setMaxAge(3600L);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        
        returnnew CorsWebFilter(source);
    }
}

五、常用过滤器与功能实现

Spring Cloud Gateway提供了许多内置的过滤器,同时也支持自定义过滤器。

1. 限流过滤器

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("rate-limit-route", r -> r
            .path("/limited/**")
            .filters(f -> f
                // 使用RequestRateLimiter过滤器实现限流
                // keyResolver指定限流的键,如IP地址
                // rateLimiter指定限流策略
                .requestRateLimiter(c -> c
                    .setKeyResolver(new IpKeyResolver())
                    // 设置每秒允许1个请求
                    .setRateLimiter(new RedisRateLimiter(1, 1))))
            .uri("lb://limited-service"))
        .build();
}

// IP地址解析器,用于获取请求的IP地址作为限流键
class IpKeyResolver implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        // 获取请求的远程地址
        return Mono.just(exchange.getRequest().getRemoteAddress().getHostString());
    }
}

2. 全局过滤器实现接口认证

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求
        ServerHttpRequest request = exchange.getRequest();
        // 获取token
        String token = request.getHeaders().getFirst("Authorization");
        
        // 验证token
        if (token == null || !validateToken(token)) {
            // 验证失败,返回401未授权错误
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 创建响应内容
            byte[] responseBytes = "{\"status\":401,\"message\":\"Unauthorized\"}".getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(responseBytes);
            // 设置响应内容类型
            response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json");
            // 返回响应
            return response.writeWith(Mono.just(buffer));
        }
        
        // 验证通过,继续执行过滤器链
        return chain.filter(exchange);
    }
    
    // 验证token的方法
    private boolean validateToken(String token) {
        // 实际项目中应该调用认证服务或使用JWT等方式验证token
        // 这里简化处理,仅作演示
        return token.startsWith("Bearer ");
    }
    
    @Override
    public int getOrder() {
        // 设置过滤器优先级,值越小优先级越高
        return0;
    }
}

3. 自定义过滤器实现日志记录

@Component
public class LoggingFilter implements GatewayFilter, Ordered {
    
    privatestaticfinal Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 记录请求开始时间
        long startTime = System.currentTimeMillis();
        
        // 获取请求信息
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        String method = request.getMethod().name();
        
        // 记录请求日志
        logger.info("Request: {} {}", method, path);
        
        // 使用过滤器链继续处理请求,并在响应处理完成后执行后续逻辑
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // 计算请求处理时间
            long duration = System.currentTimeMillis() - startTime;
            // 获取响应状态
            HttpStatus statusCode = exchange.getResponse().getStatusCode();
            // 记录响应日志
            logger.info("Response: {} {} - {} - {}ms", 
                method, path, 
                statusCode != null ? statusCode.value() : "Unknown", 
                duration);
        }));
    }
    
    @Override
    public int getOrder() {
        // 设置过滤器优先级
        return -1; // 比认证过滤器优先级高
    }
}

六、网关的高级应用

1. 灰度发布

通过网关的动态路由功能,可以实现灰度发布,将部分流量引导到新版本服务:

@Bean
public RouteLocator grayReleaseRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("gray-release", r -> r
            .path("/api/**")
            .filters(f -> f.filter((exchange, chain) -> {
                // 获取请求
                ServerHttpRequest request = exchange.getRequest();
                // 获取请求头中的灰度标记
                String gray = request.getHeaders().getFirst("X-Gray-Release");
                
                // 根据灰度标记决定路由目标
                String uri;
                if ("true".equals(gray)) {
                    // 灰度用户路由到新版本
                    uri = "lb://service-new-version";
                } else {
                    // 普通用户路由到旧版本
                    uri = "lb://service-old-version";
                }
                
                // 设置路由目标
                return chain.filter(exchange.mutate()
                    .request(request)
                    .build());
            }))
            .uri("lb://default-service"))
        .build();
}

2. 断路器集成

结合Spring Cloud Circuit Breaker,实现服务降级和熔断:

@Bean
public RouteLocator circuitBreakerRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("circuit-breaker", r -> r
            .path("/cbservice/**")
            .filters(f -> f
                // 使用断路器过滤器
                .circuitBreaker(config -> config
                    // 指定降级URI
                    .setFallbackUri("forward:/fallback")
                    // 配置断路器名称
                    .setName("myCircuitBreaker")))
            .uri("lb://circuit-service"))
        .build();
}

// 降级服务处理器
@RestController
publicclass FallbackController {
    
    @GetMapping("/fallback")
    public Mono<Map<String, String>> fallback() {
        Map<String, String> response = new HashMap<>();
        response.put("status", "error");
        response.put("message", "服务暂时不可用,请稍后再试");
        return Mono.just(response);
    }
}

七、总结与最佳实践

网关作为微服务架构中的关键组件,承担着路由转发、安全控制、限流熔断等多项重要功能。在实际应用中,我们应该注意以下几点:

  1. 合理划分职责:网关应专注于路由、过滤等功能,避免将业务逻辑放入网关。
  2. 性能优化:减少不必要的过滤器,优化过滤器执行顺序,必要时考虑使用缓存。
  3. 高可用设计:网关是系统的入口,必须保证高可用,可以通过集群部署、负载均衡等方式提高可用性。
  4. 安全防护:实施合理的认证授权机制,防范常见的安全攻击。
  5. 监控告警:对网关的请求量、响应时间、错误率等指标进行监控,及时发现并解决问题。
  6. 合理配置跨域:根据实际需求配置跨域策略,既要满足业务需求,又要保证安全性。

通过本文的介绍,相信大家对网关的概念、功能及使用方法有了更深入的了解。在实际项目中,可以根据自身需求选择合适的网关技术,并灵活运用各种过滤器和功能实现业务需求。


上篇文章
Transformer与混合专家(MoE):大型语言模型的架构对比
下篇文章
一文说清楚让LangChain大佬“开战”的MCP是什么?