CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Spring Cloud 微服务入门/课时/Spring Cloud Gateway
RawBlame
课时目录10 节

Spring Cloud 微服务入门

  1. 01微服务与 Spring Cloud
  2. 02架构模式与术语
  3. 03服务注册与 Eureka
  4. 04Spring Cloud Gateway
  5. 05Cloud Config 配置中心
  6. 06OpenFeign 服务调用
  7. 07熔断与容错
  8. 08Spring Data 扩展
  9. 09Micrometer 与可观测性
  10. 10综合练习与部署直觉

Spring Cloud Gateway

路由、过滤器、统一鉴权与限流入口。

40 分钟

Spring Cloud Gateway

这一课你将能做到

  1. 独立搭起 Gateway,把 /api/users/** 路由到 user-service、/api/problems/** 路由到 problem-service
  2. 写一个自定义 GlobalFilter 统一校验登录态,并向下游注入用户身份 Header
  3. 给对外接口配置基于 IP 的限流,说清网关过滤器链的执行顺序

为什么要学

微服务一拆多,前端就要面对 user-service:8081、problem-service:8082、judge-service:8083 一堆地址,每个服务还得各写一遍 CORS、JWT 校验、限流——重复且容易漏。API 网关把这些横切关注点收口到唯一入口:前端只认一个域名,内部怎么拆、怎么扩容前端完全无感。

本站单机版是 Nginx 把 /api 反代到 Spring Boot:8080;Gateway 就是应用层的 Nginx,但它跑在 Java 里、认识注册中心,能用代码写路由与鉴权逻辑。生产常见组合是「Nginx 终止 TLS + Gateway 做应用层路由」,两者不是二选一。

先建立图像

一次请求穿过网关的路径:

浏览器 GET /api/problems/1
   │
   ▼
Gateway:8080
   │  ① 路由匹配(Predicate):Path=/api/problems/** 命中 problem 路由
   │  ② 过滤器链(Filter Chain)pre 部分:鉴权 → 加 Header → 限流
   │  ③ 转发:lb://problem-service → LoadBalancer 选实例 → http://10.0.0.7:8082/problems/1
   │  ④ 过滤器链 post 部分:改响应、记日志
   ▼
problem-service 返回 JSON

两个核心概念:

  • Predicate(断言):决定「这个请求归哪条路由」,如 Path、Method、Header、Time。
  • Filter(过滤器):决定「转发前后做点什么」,与 Servlet Filter 思想一致,但基于 WebFlux。

跟着做

步骤 1:创建 gateway 模块

pom.xml(注意:Gateway 基于 WebFlux,绝对不能再引入 spring-boot-starter-web,否则启动直接冲突报错):

<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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>cloud-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>api-gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>
</project>

启动类就是普通 Spring Boot 启动类,无特殊注解:

package com.example.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

步骤 2:配置路由

application.yml:

server:
  port: 8080
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
        - id: problem-route
          uri: lb://problem-service
          predicates:
            - Path=/api/problems/**
          filters:
            - StripPrefix=1
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

逐项解释:

  • lb://user-service:lb 即 load balance——从 Eureka 按 user-service 这个名字查实例列表并负载均衡,这就是网关和注册中心的联动。
  • Path=/api/users/**:路径断言,命中才走这条路由。
  • StripPrefix=1:转发前去掉路径第一段,/api/users/1 变成 /users/1,恰好对上 user-service 自己的 @RequestMapping("/users")。

启动 eureka-server、user-service、problem-service、api-gateway 四个进程后访问:

curl http://localhost:8080/api/users/1      # → user-service 响应
curl http://localhost:8080/api/problems/1   # → problem-service 响应

前端从此只认 :8080 一个端口。

步骤 3:常用内置过滤器

spring:
  cloud:
    gateway:
      default-filters:            # 对所有路由生效
        - AddRequestHeader=X-Trace-Source, gateway
      routes:
        - id: problem-route
          uri: lb://problem-service
          predicates:
            - Path=/api/problems/**
          filters:
            - StripPrefix=1
            - Retry=3              # 网络异常自动重试 3 次(仅幂等 GET 场景)
            - AddResponseHeader=X-Response-By, gateway

全局 CORS 也建议在网关统一配,业务服务不再各自配置:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOriginPatterns: "https://www.codeforge.example"
            allowedMethods: "*"
            allowedHeaders: "*"
            allowCredentials: true

步骤 4:自定义 GlobalFilter 统一鉴权

package com.example.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    /** 白名单:登录、注册不校验 */
    private static final List<String> WHITE_LIST =
            List.of("/api/auth/login", "/api/auth/register");

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();
        if (WHITE_LIST.stream().anyMatch(path::startsWith)) {
            return chain.filter(exchange);
        }
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();   // pre 阶段直接短路返回
        }
        // 真实项目在此解析并校验 JWT 签名,取出 userId 后注入 Header 传给下游
        String userId = "1001"; // demo:模拟解析结果
        var mutated = exchange.getRequest().mutate()
                .header("X-User-Id", userId)
                .build();
        return chain.filter(exchange.mutate().request(mutated).build());
    }

    @Override
    public int getOrder() {
        return -100;   // 数值越小越先执行,鉴权放最前面
    }
}

下游服务从 X-User-Id Header 取用户身份,不必再解析 JWT——鉴权做一次,全站受益。

步骤 5:基于 Redis 的 IP 限流

加依赖 spring-boot-starter-data-redis-reactive,配置:

spring:
  data:
    redis:
      host: localhost
      port: 6379
  cloud:
    gateway:
      routes:
        - id: problem-route
          uri: lb://problem-service
          predicates:
            - Path=/api/problems/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10    # 每秒放行 10 个
                redis-rate-limiter.burstCapacity: 20    # 突发容量 20
                key-resolver: "#{@ipKeyResolver}"
package com.example.gateway.config;

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 RateLimitConfig {

    /** 按「IP+路径」限流:同一 IP 打不同接口互不影响 */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
                        + ":" + exchange.getRequest().getPath().value());
    }
}

超限请求会收到 429 Too Many Requests。底层是 Redis + Lua 实现的令牌桶算法,分布式环境下所有网关实例共享同一个计数器。

讲清楚要点

  1. 路由三要素:id(唯一标识)、uri(转发目标,lb://服务名 或 http://固定地址)、predicates(匹配条件)。
  2. 过滤器链有 pre/post 两个阶段:请求先按 order 从小到大走完所有 pre 逻辑 → 转发到下游 → 再按相反顺序走 post 逻辑;chain.filter() 之后的代码就是 post 阶段。
  3. GlobalFilter 与 GatewayFilter:Global 作用于所有路由,GatewayFilter 绑定单条路由(yml filters: 配的多数是这类)。
  4. lb:// 的本质:网关内置 LoadBalancer,按服务名查注册中心拿实例列表,默认轮询选一个发请求——注册中心挂了它还能靠本地缓存转发。
  5. Gateway 是响应式(WebFlux)非阻塞模型:少量线程即可扛高并发转发,代价是不能引入 spring-boot-starter-web,也别在过滤器里写阻塞调用(如同步 JDBC)。
  6. 与 Nginx 分工:Nginx 管 TLS、静态资源、粗粒度负载均衡;Gateway 管动态路由、业务级鉴权限流。两层并存是主流。

你可能会懵的地方

  • 为什么 Gateway 模块不能加 spring-boot-starter-web? web 是 Servlet(Tomcat 阻塞模型),Gateway 是 WebFlux(Netty 非阻塞模型),两者都要占据应用主类型,共存时启动即报 Spring MVC found on classpath 错误。网关里需要调 HTTP 就用 WebClient/RestClient。

  • 多个过滤器 order 相同怎么办? 按注册顺序执行,行为不保证稳定。显式实现 Ordered 接口或用 @Order 注解定死顺序:鉴权 -100 → 限流 -50 → 日志 0,一目了然。

  • StripPrefix 什么时候用、什么时候不用? 用不用取决于下游服务的真实路径。下游是 @RequestMapping("/users")、对外约定 /api/users,就 Strip 掉 1 段;如果下游直接写 @RequestMapping("/api/users"),就不 Strip。团队统一一种约定最好。

  • 网关挂了是不是全站挂了? 是单点,所以要部署多实例 + 前面挂 Nginx/LB 做高可用;路由规则存在网关自己内存里,实例之间无状态,随便横向加。

  • 路由可以不写在 yml 里吗? 可以。实现 RouteLocator Bean 用 Java DSL 写,或接 Nacos/数据库做动态路由(改配置实时生效)。小项目 yml 足够,别一开始就上动态路由。

错法对照

错法问题正解
Gateway 依赖里加了 spring-boot-starter-webServlet 与 WebFlux 冲突,启动失败只留 gateway 相关 starter,HTTP 调用用 WebClient
uri 写成 http://localhost:8081绕过注册中心,扩容即失效lb://user-service + 服务名
在 GlobalFilter 里写同步 JDBC 查权限阻塞 Netty 事件线程,吞吐暴跌过滤器内只做轻量校验,或全链路响应式
网关和业务服务都配 CORS响应头重复,浏览器报错只在网关统一配置,服务端删除
对非幂等 POST 配 Retry=3重复提交,扣款/判题重复执行重试只给幂等请求;写操作用幂等键

卡点急救

  1. 启动报 Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway:依赖冲突,删掉 spring-boot-starter-web/spring-boot-starter-tomcat,只保留 spring-cloud-starter-gateway。
  2. 转发报 503 Service Unavailable:目标服务没注册上 Eureka 或缺 loadbalancer 依赖。确认服务在 Eureka 控制台是 UP,且网关 pom 里有 spring-cloud-starter-loadbalancer。
  3. 限流永远不生效 / 一直 429:检查 Redis 连通性、key-resolver 的 Bean 名与 yml 里 #{@ipKeyResolver} 是否一致;本地调试可把 replenishRate 临时调大。

面试常问

  • 讲讲 Gateway 过滤器链的执行流程。 请求进入 → 按路由规则匹配(Predicate)→ 收集该路由的 GatewayFilter + 所有 GlobalFilter,按 order 排序 → 依次执行 pre 逻辑 → 转发下游 → 拿到响应后按逆序执行 post 逻辑。类比 Servlet Filter 责任链,但基于 Reactor 非阻塞。能画出「鉴权 pre → 转发 → 日志 post」示意即达标。

  • 网关限流怎么实现? 内置 RequestRateLimiter 过滤器 + Redis 令牌桶:以 KeyResolver 决定限流维度(IP/用户/接口),Lua 脚本在 Redis 原子地「放令牌-扣令牌」,多实例共享计数。参数:replenishRate(每秒生成令牌数)、burstCapacity(桶容量)。答出「为什么用 Lua:保证判断与扣减的原子性」是加分点。

  • Gateway 和 Nginx 怎么分工? Nginx:L7 反代、TLS 终止、静态资源、网关集群的负载均衡;Gateway:认识注册中心做 lb:// 动态路由、Java 代码级鉴权限流灰度。典型链路:客户端 → Nginx → Gateway 集群 → 微服务。

自测三问

  1. Predicate 和 Filter 各管什么?GlobalFilter 与 GatewayFilter 区别?
  2. lb://problem-service 从转发到发出请求,中间发生了什么?
  3. 为什么网关的过滤器里不能写阻塞 JDBC 调用?

小结一句话

Gateway 是微服务统一前门:断言决定「谁处理」,过滤器决定「怎么处理」,lb:// 联动注册中心实现动态路由,鉴权限流跨域都在门口做一次。

延伸

  • Spring Cloud Gateway 官方文档
  • 下一课:Cloud Config 配置中心——十几个服务的配置改一处即可生效。
加载本节练习…
← 上一课服务注册与 Eureka下一课 →Cloud Config 配置中心