Spring Cloud Gateway 服务网关详解

一、Gateway 介绍

1. 为什么要使用 Gateway?(重点)

复制代码
vue
  |
gateway------->问题:①ip和port硬编码 ②无法负载均衡
  |
|-微服务1-|  |-微服务2-|  |-微服务3-|... ...
|-微服务1-|
|-微服务1-|
   .
   .
   .

解决的问题:

  • 解耦前端与后端:前端无需知道后端服务的具体地址

  • 动态路由:通过服务名实现路由,无需硬编码 IP 和端口

  • 负载均衡:内置 Ribbon 实现客户端负载均衡

  • 统一入口:为所有微服务提供单一入口点

  • 安全控制:统一认证、授权、限流等

2. 什么是 Gateway?

Gateway 是 Spring Cloud 官方基于 WebFlux(Reactor 响应式编程模型)开发的网关组件,用于替代 Zuul。

主要功能:

  • 路由:将请求路由到对应的微服务

  • 过滤:对请求和响应进行预处理和后处理

  • 限流:防止系统被过多请求压垮

  • 熔断:服务降级和熔断保护

3. Gateway 的启动器

复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

注意: ​ 不要添加 spring-boot-starter-web依赖,因为 Gateway 基于 WebFlux 而非 Servlet。


二、Gateway 工程搭建

1. pom.xml 配置

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/>
    </parent>
    
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.3</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
    </properties>
    
    <dependencies>
        <!-- Gateway 核心依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        
        <!-- Nacos 服务发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        
        <!-- 配置管理 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        
        <!-- 健康检查 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

2. application.yml 配置

复制代码
server:
  port: 8888  # Gateway 服务端口

spring:
  application:
    name: api-gateway  # 服务名称
  
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.61.132:8848  # Nacos 服务地址
        namespace: public
        group: DEFAULT_GROUP
      config:
        server-addr: ${spring.cloud.nacos.discovery.server-addr}
        file-extension: yaml
    
    gateway:
      discovery:
        locator:
          enabled: true  # 开启从注册中心动态创建路由
          lower-case-service-id: true  # 服务名小写
      
      # 全局跨域配置
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins: "*"
            allowed-headers: "*"
            allowed-methods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allow-credentials: true
            max-age: 3600
      
      # 路由配置
      routes:
        - id: sentinel-consumer
          uri: lb://sentinel-consumer
          predicates:
            - Path=/consumer/**
          filters:
            - StripPrefix=1
        
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix=1

# 日志配置
logging:
  level:
    org.springframework.cloud.gateway: DEBUG
    reactor.netty.http.client: DEBUG

# 健康检查端点
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

3. 启动类

复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApp {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApp.class, args);
    }
}

三、路由配置

1. 内置断言工厂

Gateway 提供了多种内置的断言工厂,用于匹配 HTTP 请求的不同属性:

复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: sentinel-consumer
          uri: lb://sentinel-consumer  # 从 Nacos 获取服务列表
          predicates:  # 断言:只有所有条件都为 true 时才进行路由
            - Path=/consumer/**  # 路径匹配
            - After=2022-04-09T13:20:54.957+08:00[Asia/Shanghai]  # 时间之后
            - Before=2023-12-31T23:59:59.999+08:00[Asia/Shanghai]  # 时间之前
            - Between=2022-01-01T00:00:00.000+08:00[Asia/Shanghai],2022-12-31T23:59:59.999+08:00[Asia/Shanghai]
            - Cookie=sessionId, test  # Cookie 匹配
            - Header=X-Request-Id, \d+  # 请求头匹配
            - Host=**.example.com  # 主机名匹配
            - Method=GET,POST  # 请求方法匹配
            - Query=name, zhangsan  # 查询参数匹配
            - RemoteAddr=192.168.1.1/24  # 远程地址匹配

2. 自定义断言工厂

2.1 创建断言工厂
复制代码
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;

import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

@Component
public class AgeRoutePredicateFactory 
        extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
    
    public AgeRoutePredicateFactory() {
        super(Config.class);
    }
    
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("minAge", "maxAge");
    }
    
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            // 从请求头获取年龄
            String ageStr = exchange.getRequest().getHeaders().getFirst("Age");
            if (ageStr == null) {
                return false;
            }
            
            try {
                int age = Integer.parseInt(ageStr);
                return age >= config.minAge && age <= config.maxAge;
            } catch (NumberFormatException e) {
                return false;
            }
        };
    }
    
    @Validated
    public static class Config {
        @NotNull
        private Integer minAge;
        
        @NotNull
        private Integer maxAge;
        
        public Integer getMinAge() {
            return minAge;
        }
        
        public void setMinAge(Integer minAge) {
            this.minAge = minAge;
        }
        
        public Integer getMaxAge() {
            return maxAge;
        }
        
        public void setMaxAge(Integer maxAge) {
            this.maxAge = maxAge;
        }
    }
}
2.2 使用自定义断言工厂
复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: age-restricted-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
            - Age=18,60  # 使用自定义断言工厂,年龄在18-60之间

四、过滤器配置

1. 内置过滤器工厂

Gateway 提供了丰富的内置过滤器:

复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: sentinel-consumer
          uri: lb://sentinel-consumer
          predicates:
            - Path=/sentinel-consumer/**
          filters:
            - StripPrefix=1  # 去除路径前缀
            - AddRequestHeader=X-Request-color, blue  # 添加请求头
            - AddRequestParameter=color, blue  # 添加请求参数
            - AddResponseHeader=X-Response-color, blue  # 添加响应头
            - PrefixPath=/api  # 添加路径前缀
            - SetPath=/api/{segment}  # 设置路径
            - SetStatus=401  # 设置状态码
            - RedirectTo=302, https://example.com  # 重定向
            - RemoveRequestHeader=X-Request-Foo  # 移除请求头
            - RemoveResponseHeader=X-Response-Foo  # 移除响应头
            - RewritePath=/red/(?<segment>.*), /$\{segment}  # 重写路径
            - Retry=3  # 重试3次
            - RequestSize=5000000  # 请求大小限制(5MB)
            - RequestRateLimiter=#{@myRateLimiter}  # 限流

2. 自定义过滤器工厂

2.1 创建过滤器工厂
复制代码
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

@Component
public class LogGatewayFilterFactory 
        extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
    
    public LogGatewayFilterFactory() {
        super(Config.class);
    }
    
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("enabled");
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if (config.isEnabled()) {
                ServerHttpRequest request = exchange.getRequest();
                
                // 记录请求信息
                System.out.println("======================================");
                System.out.println("请求路径: " + request.getURI().getPath());
                System.out.println("请求方法: " + request.getMethod());
                System.out.println("请求参数: " + request.getQueryParams());
                System.out.println("请求头: " + request.getHeaders());
                System.out.println("======================================");
                
                // 在请求头中添加日志标识
                ServerHttpRequest modifiedRequest = request.mutate()
                    .header("X-Log-Enabled", "true")
                    .build();
                
                return chain.filter(exchange.mutate().request(modifiedRequest).build())
                    .then(Mono.fromRunnable(() -> {
                        // 记录响应信息
                        System.out.println("响应状态码: " + 
                            exchange.getResponse().getStatusCode());
                    }));
            }
            
            return chain.filter(exchange);
        };
    }
    
    public static class Config {
        private boolean enabled = false;
        
        public boolean isEnabled() {
            return enabled;
        }
        
        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }
}
2.2 使用自定义过滤器工厂
复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: logging-route
          uri: lb://sentinel-consumer
          predicates:
            - Path=/sentinel-consumer/**
          filters:
            - StripPrefix=1
            - Log=true  # 启用日志过滤器

3. 自定义全局过滤器

复制代码
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

@Component
public class GlobalLoginFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        
        // 获取请求路径
        String path = request.getURI().getPath();
        
        // 排除登录接口和公开接口
        if (path.contains("/auth/login") || 
            path.contains("/auth/register") ||
            path.contains("/public/")) {
            return chain.filter(exchange);
        }
        
        // 从请求头中获取token
        String token = request.getHeaders().getFirst("Authorization");
        
        if (!StringUtils.hasText(token)) {
            // 从查询参数中获取token
            token = request.getQueryParams().getFirst("token");
        }
        
        // token为空,返回未授权
        if (!StringUtils.hasText(token)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            
            String message = "{\"code\":401,\"message\":\"未授权访问,请先登录\"}";
            DataBuffer buffer = response.bufferFactory()
                .wrap(message.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(buffer));
        }
        
        // 验证token(这里简化处理,实际应该调用认证服务)
        if (!isValidToken(token)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            
            String message = "{\"code\":401,\"message\":\"Token无效或已过期\"}";
            DataBuffer buffer = response.bufferFactory()
                .wrap(message.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(buffer));
        }
        
        // 将用户信息添加到请求头中
        ServerHttpRequest newRequest = request.mutate()
            .header("X-User-Id", extractUserIdFromToken(token))
            .header("X-User-Name", extractUsernameFromToken(token))
            .build();
        
        return chain.filter(exchange.mutate().request(newRequest).build());
    }
    
    @Override
    public int getOrder() {
        return 0;  // 执行顺序,值越小优先级越高
    }
    
    private boolean isValidToken(String token) {
        // 这里应该调用认证服务验证token
        // 简化处理,假设以"Bearer "开头的token为有效
        return token.startsWith("Bearer ");
    }
    
    private String extractUserIdFromToken(String token) {
        // 从token中提取用户ID
        // 简化处理,返回固定值
        return "123";
    }
    
    private String extractUsernameFromToken(String token) {
        // 从token中提取用户名
        // 简化处理,返回固定值
        return "admin";
    }
}

五、高级配置

1. 统一异常处理

复制代码
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
    
    public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes,
                                         WebProperties.Resources resources,
                                         ApplicationContext applicationContext,
                                         ServerCodecConfigurer configurer) {
        super(errorAttributes, resources, applicationContext);
        this.setMessageWriters(configurer.getWriters());
        this.setMessageReaders(configurer.getReaders());
    }
    
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }
    
    private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        Map<String, Object> errorPropertiesMap = getErrorAttributes(request, false);
        
        int status = (int) errorPropertiesMap.getOrDefault("status", 500);
        String message = (String) errorPropertiesMap.getOrDefault("message", "Internal Server Error");
        String path = (String) errorPropertiesMap.get("path");
        
        Map<String, Object> result = new HashMap<>();
        result.put("code", status);
        result.put("message", message);
        result.put("path", path);
        result.put("timestamp", System.currentTimeMillis());
        
        return ServerResponse
            .status(HttpStatus.valueOf(status))
            .contentType(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromValue(result));
    }
}

2. 限流配置

复制代码
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

@Configuration
public class RateLimiterConfig {
    
    /**
     * 根据IP进行限流
     */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }
    
    /**
     * 根据用户ID进行限流
     */
    @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().getURI().getPath()
        );
    }
}

3. 配置文件 application-gateway.yml

复制代码
spring:
  cloud:
    gateway:
      # 默认过滤器配置
      default-filters:
        - AddRequestHeader=X-Request-Gateway, api-gateway
        - AddResponseHeader=X-Response-Gateway, api-gateway
      
      # 路由配置
      routes:
        # 用户服务路由
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/user-service/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                key-resolver: "#{@ipKeyResolver}"
                redis-rate-limiter.replenishRate: 10  # 每秒令牌数
                redis-rate-limiter.burstCapacity: 20  # 令牌桶容量
          
        # 订单服务路由
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/order-service/**
          filters:
            - StripPrefix=1
            - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/fallback/order-service
          
        # 商品服务路由
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/product-service/**
          filters:
            - StripPrefix=1
            
        # 认证服务路由
        - id: auth-service
          uri: lb://auth-service
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=0
      
      # 熔断降级配置
      hystrix:
        command:
          fallbackcmd:
            execution:
              isolation:
                thread:
                  timeoutInMilliseconds: 5000
      
      # 重试配置
      retry:
        retries: 3
        series: SERVER_ERROR
        methods: GET,POST
        exceptions: java.io.IOException, java.util.concurrent.TimeoutException

六、监控和日志

1. 集成 Spring Boot Admin

复制代码
<!-- 在pom.xml中添加 -->
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.7.0</version>
</dependency>

2. 配置监控端点

复制代码
management:
  endpoints:
    web:
      exposure:
        include: "*"
      cors:
        allowed-origins: "*"
        allowed-methods: GET,POST
  endpoint:
    health:
      show-details: always
    gateway:
      enabled: true
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: ${spring.application.name}

3. 日志配置

复制代码
logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  level:
    root: INFO
    org.springframework.cloud.gateway: DEBUG
    org.springframework.web: DEBUG
    reactor.netty: DEBUG
  file:
    name: logs/gateway.log
    max-size: 10MB
    max-history: 30

七、最佳实践

1. 生产环境建议

  • 使用配置中心(Nacos Config)管理路由配置

  • 启用熔断和降级机制

  • 配置合理的限流策略

  • 开启请求日志和监控

  • 使用HTTPS确保通信安全

  • 配置合理的超时时间

2. 性能优化

复制代码
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000
        response-timeout: 5s
        pool:
          type: elastic
          max-idle-time: 60s
          max-life-time: 60s
      metrics:
        enabled: true

3. 安全配置

复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
            .csrf().disable()
            .authorizeExchange()
                .pathMatchers("/auth/**", "/public/**").permitAll()
                .anyExchange().authenticated()
            .and()
            .oauth2ResourceServer()
                .jwt()
            .and()
            .build();
    }
}

总结

Spring Cloud Gateway 作为微服务架构的入口,提供了强大的路由、过滤、限流等功能。通过合理的配置和使用,可以构建出高性能、高可用的API网关。在实际项目中,需要根据具体业务需求选择合适的配置方案,并注意监控和安全防护。

这份笔记涵盖了Gateway的核心概念、配置方法和最佳实践,适合在CSDN等技术平台分享。

相关推荐
tsyjjOvO4 小时前
服务网关 Gateway 从入门到精通
gateway
甜鲸鱼1 天前
JWT过滤器:从单体应用到微服务架构
微服务·架构·gateway·springcloud
notfound40431 天前
解决SpringCloudGateway用户请求超时导致日志未记录情况
java·spring boot·spring·gateway·springcloud
接着奏乐接着舞2 天前
gateway
gateway
一个public的class3 天前
前后端 + Nginx + Gateway + K8s 全链路架构图解
前端·后端·nginx·kubernetes·gateway
uNke DEPH4 天前
SpringCloud Gateway 集成 Sentinel 详解 及实现动态监听Nacos规则配置实时更新流控规则
spring cloud·gateway·sentinel
ERBU DISH5 天前
当遇到 502 错误(Bad Gateway)怎么办
gateway
小超同学你好8 天前
OpenClaw 深度解析与源代码导读 · 第3篇:Gateway——常驻控制面、单端口多协议与进程骨架
人工智能·深度学习·语言模型·gateway
w6100104669 天前
Cka-2026-gateway解释
gateway·k8s·cka