SpringCloud 系列 04:Gateway 断言 / 过滤器 / 限流 一站式落地指南

在微服务架构盛行的今天,API网关早已成为整个系统的"门户"------它承载着请求路由、流量控制、安全防护、日志监控等核心职责,是客户端与微服务集群之间的唯一入口。而Spring Cloud Gateway作为Spring官方推出的第二代网关组件,凭借响应式编程的高性能、丰富的内置功能以及与Spring生态的无缝整合,彻底替代了Netflix Zuul,成为微服务架构中网关的首选方案。

Gateway是什么

1. 网关的核心定位

在微服务架构中,我们将单体应用拆分为多个独立部署、自治的小服务(如用户服务、订单服务、商品服务),这些服务分散在不同的网络地址,直接暴露给客户端会带来一系列问题:

  • 客户端需要记住所有微服务的IP/端口,维护成本极高,且服务扩缩容、故障切换时客户端无法感知;

  • 每个微服务都需要重复实现认证、授权、限流、日志等公共功能,代码冗余且难以统一标准;

  • 客户端可能需要调用多个微服务才能完成一次业务操作(如下单需要调用订单、商品、支付服务),多次请求会增加网络开销,降低用户体验;

  • 内部微服务可能使用gRPC等高效协议,无法直接对外提供HTTP接口。

API网关正是为解决这些问题而生------它位于客户端与微服务之间,充当"中间人"角色,所有客户端请求都先经过网关,由网关统一处理公共逻辑、路由到目标服务,再将服务响应汇总后返回给客户端。简单来说,网关就是微服务系统的"大门",守住大门,就能守住整个系统的稳定性和安全性。

2.Spring Cloud Gateway的定义

Spring Cloud Gateway是基于Spring 5、Project Reactor和Spring Boot 2.x构建的响应式API网关,它并非简单的请求转发工具,而是一套完整的网关解决方案。其核心设计目标是提供一种简单、高效、强大的方式来路由请求,并通过过滤器链实现对请求/响应的全方位加工处理。

关键点说明:

  • 响应式编程:基于Netty服务器和Project Reactor实现,采用非阻塞I/O模型,单个线程可处理数千级并发连接,性能远超传统的同步阻塞网关(如Zuul 1.x);

  • 原生整合Spring生态:与Spring Cloud DiscoveryClient(服务发现)、Spring Security(认证授权)、Nacos(配置中心)、Sentinel(限流熔断)等组件无缝兼容,无需额外开发集成逻辑;

  • 功能可扩展:支持自定义路由断言、自定义过滤器,可根据业务需求灵活扩展网关功能;

  • 协议支持:原生支持HTTP/2、WebSocket,适合实时通信场景(如消息推送),同时兼容HTTP/1.1。

3.Gateway与Zuul的核心差异

在Spring Cloud Gateway出现之前,Netflix Zuul是微服务网关的主流选择,但Zuul 1.x存在明显的性能瓶颈,两者的核心差异如下表所示,这也是Gateway成为首选的关键原因:

特性 Spring Cloud Gateway Zuul 1.x
底层架构 基于WebFlux的响应式编程模型,依赖Netty 基于Servlet的同步阻塞模型,依赖Tomcat
性能表现 非阻塞I/O,支持百万级并发,吞吐量是Zuul的1.6倍以上 每请求一线程,高并发下线程耗尽,响应延迟高
功能扩展 30+内置过滤器,支持动态路由、自定义断言/过滤器 过滤器类型有限,动态配置需自定义开发
协议支持 原生支持HTTP/2、WebSocket 仅支持HTTP/1.1
生态整合 与Spring Cloud组件无缝集成,适配Spring生态 依赖Netflix自有组件,整合成本高

总结:如果你的系统是高并发场景(如电商、社交),或者需要整合Spring Cloud生态组件,Spring Cloud Gateway是唯一选择;Zuul 1.x仅适用于低并发、简单场景,目前已基本被淘汰。

Gateway核心原理

Spring Cloud Gateway的所有功能都围绕"路由(Route)、断言(Predicate)、过滤器(Filter)"三大核心组件展开,这三个组件构成了Gateway的"三元组"模型,也是理解Gateway工作原理的关键。

核心流程一句话概括:客户端发送请求到Gateway,Gateway通过断言(Predicate)匹配对应的路由(Route),请求经过过滤器链(Filter)处理后,被转发到目标微服务,微服务的响应经过过滤器链反向处理后,返回给客户端。

路由(Route)

路由是Gateway的基本单元,本质上是一条"请求转发规则",每条路由都包含4个核心属性,缺一不可:

  • id:路由的唯一标识符,自定义命名(如user-service-route),用于区分不同路由,建议与目标服务名关联,便于排查问题;

  • uri:目标服务的地址,即请求最终要转发到的地址,支持两种格式:

    • 静态地址:如http://localhost:8081(直接指向具体服务实例,无负载均衡);

    • 动态地址:如lb://user-service(lb是负载均衡协议,user-service是服务名,Gateway会从注册中心获取该服务的所有实例,实现负载均衡)。

  • predicates:断言集合,是路由匹配的"条件",只有当请求满足所有断言条件时,才会触发该路由的转发;

  • filters:过滤器集合,用于在请求转发前(pre阶段)和响应返回后(post阶段)对请求/响应进行加工处理(如添加请求头、路径重写、限流等)。

示例:

java 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route  # 路由ID
          uri: lb://user-service  # 目标服务(负载均衡)
          predicates:  # 断言集合(需同时满足)
            - Path=/api/users/**  # 路径匹配断言
            - Method=GET,POST     # 请求方法断言
          filters:  # 过滤器集合
            - StripPrefix=1       # 去除路径前缀1级

断言(Predicate)

断言(Predicate)基于Java 8的Function Predicate实现,本质上是一组"匹配条件",用于判断请求是否符合当前路由的转发规则。Gateway内置了10+种断言工厂,无需自定义开发,即可满足绝大多数场景的需求,常用断言如下:

断言类型 示例 说明
Path(路径断言) - Path=/api/users/** 匹配以/api/users/开头的所有请求,**表示通配符(任意字符)
Method(方法断言) - Method=GET,POST 仅匹配GET、POST请求,不匹配PUT、DELETE等请求
Header(请求头断言) - Header=X-Token, \d+ 匹配请求头中包含X-Token,且值为数字的请求
Cookie(Cookie断言) - Cookie=name, test 匹配携带Cookie,且Cookie中name=test的请求
Query(请求参数断言) - Query=page, \d+ 匹配请求参数中包含page,且值为数字的请求(如?page=1)
时间断言(After/Before/Between) - After=2025-01-01T00:00:00+08:00 After:匹配指定时间之后的请求;Before:之前;Between:之间
Weight(权重断言) - Weight=group1, 80 用于灰度发布,同一分组内的路由按权重分配流量(如80%流量转发到该路由)

关键注意点:多个断言之间是"逻辑与"关系,只有所有断言都匹配成功,请求才会被当前路由转发;如果多个路由都匹配成功,将优先匹配配置在前面的路由。

自定义路由断言工厂

如果上面的断言不能完全满足业务需求也可以自定义断言工厂,如只能让age在(min,max)之间的人来访问

第一步:配置文件中,添加一个Age的断言配置

第二步:创建一个配置类Config用于接收参数,创建一个Config类

java 复制代码
@Data
public class Config {
    private int minAge;
    private int maxAge;
}

第三步:自定义一个断言工厂,实现断言方法

java 复制代码
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<Config> {
    public AgeRoutePredicateFactory() {
        super(Config.class);
    }
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("minAge", "maxAge");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return serverWebExchange -> {
            // 1. 从请求参数中获取 age 值
            String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");

            // 2. 校验参数:无值直接返回false(拒绝访问)
            if (!StringUtils.hasText(ageStr)) {
                return false;
            }

            try {
                // 3. 解析数字并判断是否在 [minAge, maxAge] 范围内
                int age = Integer.parseInt(ageStr);
                return age >= config.getMinAge() && age <= config.getMaxAge();
            } catch (NumberFormatException e) {
                // 4. 非数字参数返回false(避免报错)
                return false;
            }
        };
    }
}

过滤器(Filter)

过滤器(Filter)是Gateway实现请求加工、安全防护、流量控制的核心组件,它可以在请求转发前(pre阶段)和响应返回后(post阶段)执行自定义逻辑,相当于对请求/响应的"拦截器"。

根据作用范围,过滤器分为两类,两者可协同工作:

2.3.1 路由过滤器(GatewayFilter)

仅作用于"指定路由",需要在路由配置中显式声明,适合对单个路由的请求进行个性化处理。Gateway内置了30+种路由过滤器,常用过滤器:

过滤器类型 过滤器名称 配置格式 示例 核心用途
路径操作 StripPrefix StripPrefix=N(N为前缀级数) StripPrefix=1,/api/users/1→/users/1 去除请求路径指定前缀
路径操作 PrefixPath PrefixPath=/前缀 PrefixPath=/api,/users/1→/api/users/1 添加请求路径前缀
路径操作 RewritePath RewritePath=正则, 替换路径 /api/(?<seg>.*), /$\{seg} 重写请求路径
请求/响应头操作 AddRequestHeader AddRequestHeader=头名, 头值 AddRequestHeader=X-Request-Id, 123456 添加请求头
请求/响应头操作 AddResponseHeader AddResponseHeader=头名, 头值 AddResponseHeader=X-Time, ${responseTime} 添加响应头
请求/响应头操作 RemoveRequestHeader RemoveRequestHeader=头名 RemoveRequestHeader=X-Unnecessary-Header 移除无用请求头
流量控制与容错 RequestRateLimiter name: RequestRateLimiter + 令牌桶参数 replenishRate:10, burstCapacity:20 基于Redis令牌桶限流
流量控制与容错 CircuitBreaker name: 实例名, fallbackUri: 降级地址 fallbackUri: forward:/fallback/user 服务熔断降级
重定向 RedirectTo RedirectTo=状态码, 目标地址 RedirectTo=302, http://xxx.com 请求重定向

注意事项:1. 所有内置路由过滤器仅作用于"指定路由",需在路由的filters配置中显式声明;2. 若需对所有路由生效,可使用内置全局过滤器或自定义全局过滤器;3. RequestRateLimiter过滤器需依赖Redis存储令牌信息,CircuitBreaker过滤器需整合对应熔断组件。

自定义过滤器

在Spring Cloud Gateway中,自定义局部过滤器可以让你根据特定的路由添加自定义的请求处理逻辑

第一步:创建LogGatewayFilterFactory局部过滤器类:
java 复制代码
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {

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

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("consoleLog", "cacheLog");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                if(config.isCacheLog()){
                    System.out.println("cacheLog已开启");
                }
                if(config.isConsoleLog()){
                    System.out.println("consoleLog已开启");
                }
                return chain.filter(exchange);
            }
        };
    }

    @Data
    @NoArgsConstructor
    public static class Config {
        private boolean consoleLog;
        private boolean cacheLog;
    }
}

这段代码实现了一个自定义的 GatewayFilterFactory (网关过滤器工厂),用于在 Spring Cloud Gateway 网关中根据配置动态开启 / 关闭两种日志记录功能:控制台日志 (consoleLog) 和缓存日志 (cacheLog)。它遵循 Spring Cloud Gateway 的扩展规范,允许你在配置文件中灵活配置过滤器的行为。

  1. 类定义与继承
  • extends AbstractGatewayFilterFactory<Config>:继承网关提供的抽象过滤器工厂类,<Config>指定该过滤器的配置类(即内部的Config类),这是自定义网关过滤器的标准方式。

2.构造方法

java 复制代码
public LogGatewayFilterFactory() {
    super(Config.class);
}

调用父类构造方法,传入配置类Config.class,目的是让父类知道该过滤器工厂使用哪个配置类来接收外部配置参数(比如 yml/properties 中的配置)。

3.shortcutFieldOrder 方法

java 复制代码
@Override
public List<String> shortcutFieldOrder() {
    return Arrays.asList("consoleLog", "cacheLog");
}
  • 这是网关过滤器工厂的核心方法之一,用于定义配置参数的快捷顺序
  • 作用:当你在配置文件中用简洁方式配置过滤器时(比如- Log= true,false),网关会按照这个列表的顺序,将配置值依次赋值给Config类中的consoleLogcacheLog属性。
  • 如果不重写这个方法,就只能用全量配置方式(name: Log, args: {consoleLog: true, cacheLog: false}),重写后支持简洁配置,更易用。

4.. apply 方法(核心过滤逻辑)

  • apply(Config config):接收外部配置的Config对象(包含consoleLogcacheLog的开关值),返回一个GatewayFilter实例。
  • GatewayFilter.filter():网关过滤器的核心执行方法,每次请求经过网关时都会触发:
    • ServerWebExchange exchange:封装了网关请求的所有上下文信息(请求头、请求参数、响应等),是网关中获取请求 / 响应数据的核心对象。
    • GatewayFilterChain chain:过滤器链,chain.filter(exchange)表示执行下一个过滤器(如果有的话),必须调用这个方法,否则请求会被拦截,无法转发到下游服务
    • 逻辑:根据配置的开关值,判断是否打印对应日志,然后放行请求。

5.Config 内部配置类

  • 作用:作为过滤器的配置载体,接收外部配置的consoleLogcacheLog开关值,供过滤逻辑使用。
第二步:在配置文件中自定义过滤器

你可以在网关的 yml 配置中这样使用这个过滤器:

java 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: demo_route
          uri: http://localhost:8081
          predicates:
            - Path=/demo/**
          filters:
            # 简洁配置(依赖shortcutFieldOrder):第一个值对应consoleLog,第二个对应cacheLog
            - Log=true,true
            # 全量配置(不依赖shortcutFieldOrder)
            # - name: Log
            #   args:
            #     consoleLog: true
            #     cacheLog: false

第三步:测试启动Gateway应用程序,访问匹配的路径请求,观察控制台输出,验证自定义过滤器是否正常工作

全局过滤器(GlobalFilter)

作用于"所有路由",无需在路由中显式声明,自动对所有经过网关的请求生效,适合处理全局公共逻辑(如认证授权、日志记录、跨域处理)。

Gateway内置了一些全局过滤器(如负载均衡过滤器、路由匹配过滤器),同时支持自定义全局过滤器,只需实现GlobalFilter和Ordered接口即可(Ordered接口用于指定过滤器执行顺序)。

在filter包中,创建自定义全局过滤器类,实现GlobalFiter和Ordered接口:

java 复制代码
@Component
public class AuthorizationGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        // 从请求头中获取token
        String token = request.getHeaders().getFirst("Authorization");
        System.out.println("全局过滤器执行...检查token是否有效");

        // 验证token
        if (!isValidToken(token)) {
            // 如果token无效,返回401错误
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 设置响应头(可选,指定JSON格式)
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

            // 构建错误响应信息
            String errorMsg = "{\"code\":401,\"message\":\"Token无效\"}";
            System.out.println("错误信息:" + errorMsg);

            //创建DataBuffer,无需强制类型转换
            DataBuffer buffer = response.bufferFactory().wrap(errorMsg.getBytes(StandardCharsets.UTF_8));
            // 写入响应并完成响应
            return response.writeWith(Flux.just(buffer))
                    .then(response.setComplete()); // 确保响应完成
        }
        // token有效,继续执行过滤器链
        return chain.filter(exchange);
    }

    private boolean isValidToken(String token) {
        // 简单模拟验证(实际开发中需替换为真实的token校验逻辑,如JWT解析)
        return token != null && !token.isEmpty();
    }

    @Override
    public int getOrder() {
        // 过滤器执行顺序,数值越小越先执行
        return -100;
    }
}

AuthorizationGlobalFilter 类实现了 GlobalFilterOrdered 接口,用于在 Spring Cloud Gateway 中进行全局 Token 鉴权。

核心方法与逻辑
  1. getOrder() 方法

    • 用于指定过滤器的执行顺序,值越小越先执行
    • 示例中返回 -100,确保鉴权逻辑优先于路由、负载均衡等核心流程执行,实现 "先鉴权,后转发"。
  2. filter(ServerWebExchange exchange, GatewayFilterChain chain) 方法

    • 这是过滤器的核心逻辑:
      1. 从请求头中获取 Authorization 字段的 Token。
      2. 调用 isValidToken 方法验证 Token 是否有效。
      3. 如果 Token 无效,返回 401 错误响应(JSON 格式),终止请求。
      4. 如果 Token 有效,继续执行过滤器链,将请求转发到后端服务。
  3. isValidToken(String token) 方法

    • 这是一个简单的模拟验证逻辑,示例代码为:

      复制代码
      private boolean isValidToken(String token) {
          // 这里简单模拟验证逻辑,实际应用中需要根据具体业务实现
          return token != null && !token.isEmpty();
      }
    • 在实际应用中,需要替换为真实的鉴权逻辑,例如:

      • 解析 JWT Token,校验签名和过期时间。
      • 从 Redis 中查询 Token 是否存在、是否过期。
      • 调用认证中心接口校验 Token 有效性。

全局跨域

在前后端分离架构中,前端页面(如Vue、React)与网关不在同一域名下时,会触发浏览器的同源策略限制,导致跨域请求失败(常见报错:Access-Control-Allow-Origin缺失)。Gateway作为请求入口,可通过全局跨域配置统一解决所有路由的跨域问题,无需在每个微服务中单独配置跨域。

核心说明:Gateway的跨域配置基于CORS(跨域资源共享)协议,通过配置允许的域名、请求方法、请求头等,实现跨域访问授权,以下是生产级标准配置。

1. 全局跨域配置实现(两种方式,任选其一)

方式一:配置文件方式(简洁高效,推荐生产使用),直接在application.yml中添加跨域配置,无需编写代码:

bash 复制代码
spring:
  cloud:
    gateway:
      # 全局跨域配置(作用于所有路由)
      globalcors:
        cors-configurations:
          # 匹配所有请求路径(/**表示所有路径)
          '[/**]':
            allowed-origins:  # 允许跨域的前端域名(生产环境需指定具体域名,避免*带来的安全风险)
              - "http://localhost:8088"  # 本地前端开发域名(示例)
              - "https://xxx-frontend.com"  # 生产前端域名(替换为实际域名)
            allowed-methods:  # 允许跨域的请求方法(GET/POST/PUT/DELETE等,*表示所有方法)
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowed-headers:  # 允许跨域的请求头(*表示所有请求头,支持自定义请求头如X-Token)
              - '*'
            allow-credentials: true  # 是否允许携带Cookie(前后端分离常用,需与前端配置一致)
            max-age: 3600  # 跨域请求的有效期(单位:秒),表示1小时内无需重复发起预检请求(OPTIONS请求)

方式二:代码配置方式(灵活可扩展,适合需要动态调整跨域规则的场景),通过自定义配置类实现:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

/**
 * 全局跨域配置类(代码方式,与配置文件方式二选一)
 */
@Configuration
public class GlobalCorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        // 1. 配置跨域核心规则
        CorsConfiguration corsConfig = new CorsConfiguration();
        // 允许跨域的域名(生产环境替换为具体域名,禁止使用*)
        corsConfig.addAllowedOrigin("http://localhost:8088");
        corsConfig.addAllowedOrigin("https://xxx-frontend.com");
        // 允许跨域的请求方法
        corsConfig.addAllowedMethod("GET");
        corsConfig.addAllowedMethod("POST");
        corsConfig.addAllowedMethod("PUT");
        corsConfig.addAllowedMethod("DELETE");
        corsConfig.addAllowedMethod("OPTIONS");
        // 允许跨域的请求头
        corsConfig.addAllowedHeader("*");
        // 允许携带Cookie
        corsConfig.setAllowCredentials(true);
        // 跨域请求有效期(3600秒)
        corsConfig.setMaxAge(3600L);

        // 2. 配置跨域规则作用的路径(/**表示所有路径)
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", corsConfig);

        // 3. 返回跨域过滤器(全局生效)
        return new CorsWebFilter(source);
    }
}
2. 关键注意事项
  • 禁止在生产环境使用 allowed-origins: "*":* 表示允许所有域名跨域,存在安全风险(如CSRF攻击),需指定具体的前端域名(如https://xxx-frontend.com);

  • 前后端配置一致:若开启 allow-credentials: true(允许携带Cookie),前端请求时需设置 withCredentials: true(如Axios配置),否则跨域请求会失败;

  • 避免重复配置:Gateway全局跨域配置生效后,无需在各个微服务中再配置跨域(重复配置会导致跨域响应头冲突,出现多次Access-Control-Allow-Origin的报错);

  • 预检请求处理:OPTIONS请求是浏览器发起的跨域预检请求,需在allowed-methods中添加OPTIONS,否则预检请求会被拦截,导致跨域失败;

  • 兼容性说明:该配置支持HTTP/1.1和HTTP/2,适配主流浏览器(Chrome、Firefox、Edge等),无需额外处理浏览器兼容性问题。

网关限流

结合Redis实现限流

第一步:在pom中导入,Redis的依赖

XML 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

第二步:修改Applicatio.yml,增加Redis的配置:

第三步:创建限流规则,创建包config包中创建一个限流规则定义类,实现根据IP进行限流的功能:

第四步:配置限流过滤器,在网关项目的配置文件中,添加以下配置信息

配置参数说明:

  • replenishRate:每秒向令牌桶中补充的令牌数,代表系统的稳定处理能力。
  • burstCapacity:令牌桶的最大容量,代表系统能承受的最大突发流量。
  • keyResolver:指定用于限流的 Key,这里我们引用了自定义的 IpAddressKeyResolver
相关推荐
忍者必须死2 小时前
ConcurrentHashMap源码解析
java
闻哥2 小时前
23种设计模式深度解析:从原理到实战落地
java·jvm·spring boot·设计模式·面试
禹凕2 小时前
MYSQL——基础知识(NULL 值处理)
数据库·mysql
码云数智-大飞2 小时前
SQL Server 无法启动?常见原因及详细解决方法指南
数据库
8486981192 小时前
MySQL 只读库踩坑实录:为什么 INSERT/UPDATE 不报错,DELETE 却直接炸了?
数据库·mysql·hibernate
wuqingshun3141592 小时前
java创建对象的方式
java·开发语言
求知摆渡2 小时前
Spring AI 多模型对话 Demo 实战:OpenAI/Ollama 一套接口、Redis 会话记忆、SSE 流式输出、AOP 日志打点
java·spring
没事偷着乐琅2 小时前
二、Pandas 是啥 是数据库吗?
数据库·pandas
二十雨辰2 小时前
[英语]-介词和动词
开发语言