Spring Cloud Gateway速览

注意
本文最后更新于 2024-05-09,文中内容可能已过时。

一、Gateway 基础介绍

1、是什么?

SpringCloud Gateway 是 SpringCloud 官方新一代网关,用于请求统一入口、路由转发、过滤拦截,替代旧版 Zuul。

2、作用 🔴

  • 统一入口:所有微服务请求统一经过网关,对外只暴露一个端口。

  • 路由转发:根据规则把请求分发到不同微服务。

  • 过滤拦截:权限校验、Token校验、请求过滤、黑名单。

  • 限流熔断:配合Sentinel实现接口限流、防刷。

  • 统一处理:跨域、日志、请求重写、重试、负载均衡。

3、为什么不用 Zuul?Gateway 优势

  • Zuul 基于 Tomcat 阻塞IO,性能差。

  • Gateway 基于 Netty + 异步非阻塞,高并发、吞吐量高。

  • Gateway 支持动态路由、内置功能更强、Spring官方主推。

二、Gateway 三大核心组件 🔴

1、Route(路由)

路由 = 唯一ID + 请求匹配规则 + 转发目标地址

作用:匹配客户端请求,转发到指定微服务。

2、Predicate(断言、匹配规则)

判断当前请求是否满足路由条件,满足就转发。

常用内置断言

  • Path:根据请求路径匹配(最常用)

  • Method:根据请求方式 GET/POST 匹配

  • Header:判断请求头是否包含指定参数

  • Query:判断请求参数

  • Cookie:判断Cookie信息

3、Filter(过滤器)

进入路由之前、转发之后的请求做修改、拦截、处理。

过滤器分类

  1. 局部过滤器:只对当前某一个路由生效。

  2. 全局过滤器:对所有路由生效(重点)。

常用内置过滤器

  • StripPrefix:去除路径前缀(项目最常用)

  • RewritePath:重写请求路径

  • AddRequestHeader:添加请求头

  • Retry:请求失败重试

三、Gateway 执行流程 🔴

  1. 客户端发起请求,首先进入 Gateway。
  2. 网关接收请求,进入 路由断言 Predicate
  3. 判断请求是否匹配路由规则,匹配成功继续往下走。
  4. 执行 过滤器链 Filter(前置过滤器)。
  5. 负载均衡、转发到后端微服务。
  6. 服务处理完毕返回结果,执行 后置过滤器
  7. 返回响应给客户端。 一句话总结:先断言、再过滤、再转发、最后后置处理。

四、全局过滤器(工作最常用)

1、使用场景

  • 统一 Token 登录校验

  • 统一鉴权、拦截非法请求

  • 统一打印请求日志、耗时统计

  • 黑名单拦截、防刷

2、执行顺序规则

通过 order 值控制顺序,数值越小优先级越高。

五、Gateway 跨域配置(面试高频坑点)

1、为什么出现跨域?

浏览器同源策略:协议、域名、端口任意一个不同,就跨域。

2、Gateway 跨域解决方案(工作标准写法)

  • 在网关统一配置跨域,不要在每个微服务配置。

  • 允许所有请求头、允许所有请求方式、允许携带Cookie。

  • 放行预检 OPTIONS 请求。

六、Gateway + Nacos 动态路由

1、静态路由

写死在 yml 配置文件中,修改需要重启服务,生产不用。

2、动态路由(生产常用)

把路由规则配置到 Nacos配置中心,动态刷新、无需重启网关。

适合线上频繁变更路由、灰度发布、临时拦截。

七、Gateway 负载均衡

  • Gateway 内置 SpringCloud LoadBalancer

  • 默认轮询策略。

  • 自动从 Nacos 服务注册中心拉取服务列表,实现负载转发。

八、Gateway 常见工作坑点

  1. 跨域重复问题:网关配置跨域,微服务不能再配置,否则报错。

  2. 请求头丢失:网关默认过滤部分请求头,需要手动放开。

  3. Post请求传参异常:Netty解析body流只能读取一次,需要缓存请求体。

  4. 过滤器顺序错乱:一定要设置order值,避免执行顺序混乱。

  5. 高并发下连接数爆满:调整Netty线程池、连接池参数。

九、总结

Gateway 是 SpringCloud 异步非阻塞网关,核心三大组件是路由、断言、过滤器;执行流程为先断言匹配路由,再执行过滤器链,最后转发服务;生产一般配合Nacos做动态路由,网关统一做跨域、鉴权、限流、日志处理,相比Zuul性能更好,适合高并发微服务架构。

配置案例

Gateway配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

spring:
  # 网关不注册服务自身,只做转发
  cloud:
    gateway:
      # 开启服务发现自动路由(从nacos拉服务列表)
      discovery:
        locator:
          enabled: true       # 开启自动根据服务名路由
          lower-case-service-id: true

      # 全局默认过滤器(所有路由都生效)
      default-filters:
        - name: StripPrefix   # 统一去掉前缀
          args:
            parts: 1
        - name: Retry        # 失败重试
          args:
            retries: 3
            statuses: 500,502,503,504

      # 自定义路由规则
      routes:
        # 路由1:用户服务
        - id: user-service-route
          # lb:// 开启负载均衡,从nacos拿服务实例
          uri: lb://user-service
          predicates:
            - Path=/user/**                # 路径断言
            - Method=GET,POST              # 请求方式断言
          filters:
            - RewritePath=/user/(?<path>.*), /$\{path}  # 路径重写
            - AddRequestHeader=X-From,Gateway           # 添加请求头
            - AddResponseHeader=X-Source,Cloud-Gateway  # 添加响应头

        # 路由2:订单服务
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/order/**
          filters:
            - StripPrefix=1

# 全局跨域配置
spring:
  webflux:
    cors:
      allowed-origins: "*"
      allowed-methods: GET,POST,PUT,DELETE,OPTIONS
      allowed-headers: "*"
      allow-credentials: true
      max-age: 3600

全局过滤器(Token 鉴权 + 日志 + 拦截)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import reactor.core.publisher.Mono;

@Configuration
public class GatewayFilterConfig {

    /**
     * 全局日志+Token校验过滤器
     * Order数值越小,优先级越高
     */
    @Bean
    @Order(-100)
    public GlobalFilter authLogGlobalFilter() {
        return (exchange, chain) -> {
            // 1. 获取请求路径、请求头
            String path = exchange.getRequest().getPath().value();
            String token = exchange.getRequest().getHeaders().getFirst("Token");

            // 2. 放行登录/注册等无需鉴权接口
            if (path.contains("/login") || path.contains("/register")) {
                return chain.filter(exchange);
            }

            // 3. 无Token直接拦截返回401
            if (token == null || token.isBlank()) {
                exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }

            // 4. 有Token放行,继续往下走
            return chain.filter(exchange);
        };
    }
}