第五篇:网关篇 — Spring Cloud Gateway

目标 :掌握 API 网关的路由、过滤、限流与鉴权
学习时长:1~2 周


目录

  1. [为什么需要 API 网关](#为什么需要 API 网关)
  2. [Spring Cloud Gateway 架构](#Spring Cloud Gateway 架构)
  3. 路由配置详解
  4. 断言(Predicate)
  5. 过滤器(Filter)
  6. 全局过滤器
  7. 网关鉴权
  8. 网关限流
  9. [跨域处理 CORS](#跨域处理 CORS)
  10. 动态路由
  11. [Gateway vs Nginx](#Gateway vs Nginx)
  12. 面试高频题

1. 为什么需要 API 网关

复制代码
没有网关(各客户端直连各服务):
                    ┌──────────┐
  ┌──── App ──────→ │user:9001 │
  │                 └──────────┘
  │  ┌── H5 ──────→ ┌──────────┐
  │  │              │order:9002│
  │  │              └──────────┘
  └──┴── 小程序 ──→ ┌──────────┐
                    │prod:9003 │
                    └──────────┘
  问题:客户端需要知道每个服务地址、各自鉴权、CORS 各自处理

有了网关:
  App / H5 / 小程序 → 统一访问 gateway:9000
                              ↓(路由)
                  user:9001 / order:9002 / prod:9003
  优点:单一入口、统一鉴权、统一限流、统一日志

网关核心职责

职责 说明
路由转发 将请求路由到对应的微服务
负载均衡 结合注册中心,自动负载均衡
鉴权 统一 JWT 验证,无需每个服务重复实现
限流 保护后端服务,防止流量冲击
日志 统一记录请求日志、耗时
CORS 跨域统一处理
协议转换 HTTP → gRPC,WebSocket 代理

2. Spring Cloud Gateway 架构

复制代码
客户端请求
    ↓
[Gateway Handler Mapping] → 匹配路由(Predicate)
    ↓
[Gateway Web Handler]
    ↓
[Filter Chain(Pre 阶段)] → 全局过滤器 + 路由过滤器(前置逻辑)
    ↓
[后端服务](Proxied Service)
    ↓
[Filter Chain(Post 阶段)] → 全局过滤器 + 路由过滤器(后置逻辑)
    ↓
响应给客户端

⚠️ 重要 :Spring Cloud Gateway 基于 WebFlux(响应式) ,不能引入 spring-boot-starter-web(Servlet),两者冲突!


3. 路由配置详解

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: user-route            # 路由唯一 ID
          uri: lb://user-service    # lb:// = 负载均衡,从 Eureka/Nacos 获取实例
          predicates:
            - Path=/users/**        # 路径断言
          filters:
            - StripPrefix=1         # 去掉 /users 前缀

URI 的几种写法

yaml 复制代码
uri: lb://user-service               # 负载均衡(推荐)
uri: http://192.168.1.100:9001       # 直连(固定 IP)
uri: https://api.external.com        # 外部服务代理
uri: ws://chat-service               # WebSocket

4. 断言(Predicate)

断言决定路由是否匹配,可组合使用:

yaml 复制代码
routes:
  - id: demo-route
    uri: lb://user-service
    predicates:
      # 路径匹配(最常用)
      - Path=/api/users/**

      # 方法匹配
      - Method=GET,POST

      # Header 匹配
      - Header=X-Api-Version, v2         # Header 中 X-Api-Version = v2

      # 查询参数匹配
      - Query=channel, mobile             # ?channel=mobile

      # Host 匹配
      - Host=**.example.com

      # 时间范围(只在指定时间后才路由)
      - After=2024-01-01T00:00:00+08:00[Asia/Shanghai]

      # 权重(用于灰度发布)
      - Weight=service-group, 80          # 80% 流量(配合另一个 weight=20 的路由)

      # IP 白名单
      - RemoteAddr=192.168.0.0/24

5. 过滤器(Filter)

内置 GatewayFilter

yaml 复制代码
filters:
  # 去掉路径前缀(/users/api/users → /api/users)
  - StripPrefix=1

  # 添加路径前缀(/api → /v2/api)
  - PrefixPath=/v2

  # 路径重写(正则替换)
  - RewritePath=/users/(?<segment>.*), /$\{segment}

  # 添加/删除请求头
  - AddRequestHeader=X-Source, gateway
  - RemoveRequestHeader=X-Internal-Key

  # 添加/删除响应头
  - AddResponseHeader=X-Response-Time, ${spring.application.name}

  # 限流(需 Redis)
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 10    # 每秒填充令牌数
      redis-rate-limiter.burstCapacity: 20    # 令牌桶容量
      redis-rate-limiter.requestedTokens: 1   # 每请求消耗令牌数

  # 重试
  - name: Retry
    args:
      retries: 3
      statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
      methods: GET
      backoff:
        firstBackoff: 10ms
        maxBackoff: 50ms

  # 熔断(集成 Resilience4j)
  - name: CircuitBreaker
    args:
      name: userServiceCB
      fallbackUri: forward:/fallback/user

6. 全局过滤器

java 复制代码
/**
 * 全局日志 + 鉴权过滤器
 * 对所有路由生效,无需在每个路由单独配置
 */
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    // 白名单(不需要鉴权的路径)
    private static final Set<String> WHITE_LIST = Set.of(
            "/users/api/auth/login",
            "/users/api/auth/register",
            "/actuator/health"
    );

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        // 1. 白名单放行
        if (WHITE_LIST.contains(path)) {
            return chain.filter(exchange);
        }

        // 2. 获取 Token
        String token = request.getHeaders().getFirst("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            return unauthorized(exchange, "缺少认证 Token");
        }

        // 3. 验证 Token
        String jwt = token.substring(7);
        try {
            String userId = JwtUtil.getUserId(jwt);
            // 4. 将用户信息传递到下游服务(通过 Header)
            ServerHttpRequest mutatedRequest = request.mutate()
                    .header("X-User-Id", userId)
                    .build();
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        } catch (Exception e) {
            return unauthorized(exchange, "Token 无效或已过期");
        }
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        String body = "{\"code\":401,\"message\":\"" + message + "\"}";
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -100;  // 越小越先执行
    }
}

7. 网关鉴权

统一 JWT 鉴权流程

复制代码
客户端请求携带 JWT Token
    ↓
Gateway 全局过滤器拦截
    ↓
验证 Token(签名、过期时间)
    ↓
解析 Token 中的用户信息(userId、roles)
    ↓
将用户信息添加到 Header(X-User-Id、X-User-Roles)
    ↓
转发到下游服务(下游服务直接读 Header,无需再验 Token)
    ↓
下游服务信任 Gateway 传递的用户信息
java 复制代码
// 下游服务(user-service / order-service)
@GetMapping("/api/orders/my")
public Result<List<Order>> myOrders(
        @RequestHeader("X-User-Id") String userId) {  // 从 Gateway 传来的 Header
    return Result.success(orderService.listByUserId(Long.parseLong(userId)));
}

8. 网关限流

基于 Redis 的令牌桶限流

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: rate-limited-route
          uri: lb://user-service
          predicates:
            - Path=/users/**
          filters:
            - name: RequestRateLimiter
              args:
                # 令牌桶:每秒填充10个令牌,桶容量20个
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                redis-rate-limiter.requestedTokens: 1
                # 限流 Key(默认按 IP)
                key-resolver: "#{@ipKeyResolver}"
java 复制代码
@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.justOrEmpty(
                exchange.getRequest().getHeaders().getFirst("X-User-Id")
        ).defaultIfEmpty("anonymous");
    }

    // 按 API 路径限流
    @Bean
    public KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }
}

9. 跨域处理 CORS

网关统一处理 CORS,下游服务无需重复配置:

yaml 复制代码
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':                          # 对所有路由生效
            allowed-origins:
              - "http://localhost:3000"     # 前端开发地址
              - "https://app.example.com"   # 生产地址
            allowed-methods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowed-headers: "*"
            allow-credentials: true         # 允许携带 Cookie
            max-age: 3600                   # 预检请求缓存时间

10. 动态路由

从数据库或配置中心动态加载路由,支持不重启更新:

java 复制代码
@Service
@RequiredArgsConstructor
public class DynamicRouteService implements ApplicationEventPublisherAware {

    private final RouteDefinitionWriter routeDefinitionWriter;
    private ApplicationEventPublisher publisher;

    /** 添加路由 */
    public void add(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));  // 触发路由刷新
    }

    /** 删除路由 */
    public void delete(String routeId) {
        routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }
}

11. Gateway vs Nginx

维度 Spring Cloud Gateway Nginx
语言 Java C
编程模型 响应式(WebFlux) 多进程/多线程
性能 高(非阻塞) 极高
服务发现 ✅ 集成 Eureka/Nacos ❌ 需要手动 upstream
动态路由 ✅ 代码实时更新 需要 reload 或 Lua
限流 ✅ Redis 令牌桶 ✅ ngx_limit_req
鉴权 ✅ Java 编写 Lua 或外部认证服务
扩展性 高(Java 生态) 依赖 Lua/插件
定位 微服务业务网关 流量接入层

💡 最佳实践:Nginx 作为流量入口(SSL 卸载、静态资源)→ Gateway 作为业务网关(路由、鉴权、限流)→ 微服务


12. 面试高频题

Q1:Spring Cloud Gateway 的执行流程是什么?

请求 → HandlerMapping 匹配路由(Predicate 断言)→ WebHandler → Filter Chain(Pre 阶段:鉴权、限流)→ 代理转发后端服务 → Filter Chain(Post 阶段:响应处理)→ 响应客户端。

Q2:Gateway 和 Zuul 的区别?

Zuul 基于 Servlet 阻塞 I/O;Gateway 基于 WebFlux 非阻塞异步,吞吐量更高。Zuul 1.x 已停更,Spring Cloud 推荐使用 Gateway。

Q3:GlobalFilter 和 GatewayFilter 的区别?

GlobalFilter 对所有路由生效(全局鉴权、日志、限流);GatewayFilter 只对特定路由生效(需在路由配置中添加)。内置过滤器(StripPrefix、AddRequestHeader等)都是 GatewayFilter。

Q4:如何在 Gateway 实现灰度发布?

使用 Weight 断言:配置两个路由(同一服务的 v1 和 v2),分别设置权重80%和20%,实现部分流量切到新版本。也可以通过 Header(如 X-Version: v2)路由到指定版本。

Q5:Gateway 的限流是如何实现的?

RequestRateLimiter 过滤器基于 Redis 令牌桶算法:每个时间窗口向桶中填充固定数量的令牌,请求到来时消耗令牌,桶为空时拒绝请求。Lua 脚本保证 Redis 操作的原子性。


上一篇:04_配置中心篇 | 下一篇:06_可靠性篇


13. 专家级:Gateway 核心原理(WebFlux 响应式)

知识点 1:为什么 Gateway 基于 WebFlux

传统 Servlet 模型是**"一请求一线程"**:线程在等待后端服务响应时被阻塞,无法服务其他请求。

WebFlux 是响应式非阻塞模型:基于 Reactor/Netty,少量线程(CPU核数×2)处理大量并发,线程不因 I/O 阻塞。

text 复制代码
Servlet(Spring MVC):
  10000 并发 → 需要 10000 个线程(等待 I/O 时线程闲置)
  线程内存开销大,上下文切换频繁

WebFlux(Gateway):
  10000 并发 → 只需 16 个线程(I/O 事件驱动,线程不阻塞)
  更低延迟,更高吞吐量

重要限制 :正因为 WebFlux 是非阻塞的,Gateway 工程中不能使用任何阻塞 API(如 JDBC、传统 RestTemplate、同步 Feign),否则阻塞 Netty 的 EventLoop 线程,导致性能灾难。

知识点 2:过滤器的 Pre/Post 执行顺序

text 复制代码
请求进入 → Pre 过滤器(order 越小越先执行)
                ↓
            路由到后端服务
                ↓
         Post 过滤器(order 越小越后执行,即越小越靠近响应端)

全局过滤器 Order 优先级建议:
  -100: 全局日志(最先执行,最后收尾)
   -10: 全局鉴权(认证通过才路由)
     0: 全局限流
   100: 响应处理

知识点 3:完整的全局日志过滤器(含请求耗时、TraceId)

java 复制代码
@Slf4j
@Component
public class AccessLogGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String traceId = UUID.randomUUID().toString().replace("-", "");
        long startTime = System.currentTimeMillis();

        // Pre 阶段:注入 TraceId 到下游请求头
        ServerHttpRequest mutatedRequest = request.mutate()
            .header("X-Trace-Id", traceId)
            .build();

        // 记录请求信息
        log.info("[GW-IN] traceId={} method={} path={} ip={}",
            traceId, request.getMethod(),
            request.getURI().getPath(),
            getClientIp(request));

        return chain.filter(exchange.mutate().request(mutatedRequest).build())
            .then(Mono.fromRunnable(() -> {
                // Post 阶段:记录响应信息和耗时
                long cost = System.currentTimeMillis() - startTime;
                int statusCode = exchange.getResponse().getStatusCode() != null
                    ? exchange.getResponse().getStatusCode().value() : -1;
                log.info("[GW-OUT] traceId={} status={} costMs={}",
                    traceId, statusCode, cost);
            }));
    }

    private String getClientIp(ServerHttpRequest request) {
        String ip = request.getHeaders().getFirst("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            InetSocketAddress addr = request.getRemoteAddress();
            ip = addr != null ? addr.getAddress().getHostAddress() : "unknown";
        }
        return ip.contains(",") ? ip.split(",")[0].trim() : ip;
    }

    @Override
    public int getOrder() {
        return -100;  // 最高优先级
    }
}

14. 专家级:Gateway 防护安全体系

知识点:网关层的安全防御

text 复制代码
防御层次(从外到内):

Layer 1 - Nginx/CDN 层:
  - DDoS 防护(流量清洗)
  - IP 黑名单(地区封锁)
  - SSL 卸载

Layer 2 - Gateway 层:
  - JWT 签名验证(防伪造)
  - 接口签名验证(防篡改)
  - IP 限流(防刷)
  - 用户限流(防滥用)
  - SQL 注入过滤
  - XSS 过滤

Layer 3 - 业务服务层:
  - 权限校验(RBAC)
  - 业务参数校验
  - 幂等性保障

SQL 注入与 XSS 过滤器(Gateway 层)

java 复制代码
@Component
public class SecurityGlobalFilter implements GlobalFilter, Ordered {

    // SQL 注入关键词
    private static final Pattern SQL_INJECTION_PATTERN = Pattern.compile(
        "(?i)(union|select|insert|update|delete|drop|exec|xp_|--|;|/\\*|\\*/)",
        Pattern.CASE_INSENSITIVE
    );

    // XSS 脚本关键词
    private static final Pattern XSS_PATTERN = Pattern.compile(
        "<script[^>]*>.*?</script>|javascript:|on\\w+\\s*=",
        Pattern.CASE_INSENSITIVE | Pattern.DOTALL
    );

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // 检查 URL 查询参数
        String queryString = request.getURI().getRawQuery();
        if (queryString != null) {
            if (SQL_INJECTION_PATTERN.matcher(queryString).find()) {
                return badRequest(exchange, "请求参数包含非法字符");
            }
            if (XSS_PATTERN.matcher(queryString).find()) {
                return badRequest(exchange, "请求参数包含不安全内容");
            }
        }

        return chain.filter(exchange);
    }

    private Mono<Void> badRequest(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.BAD_REQUEST);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        String body = String.format("{\"code\":400,\"message\":\"%s\"}", message);
        DataBuffer buffer = response.bufferFactory()
            .wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -90;
    }
}

15. 面试高频:专家追问题

Q:Gateway 如何将用户信息传递给下游服务,下游如何信任?

原则 :Gateway 鉴权通过后,将用户信息(userId、roles)写入请求 Header(如 X-User-Id),下游服务直接读取 Header,不再验证 Token。下游服务应拒绝来自非 Gateway 的直接请求(通过内网隔离或共享密钥 Header 实现)。

Q:Gateway 高可用如何保证?

Gateway 本身是无状态的,限流用的 Redis 是外置存储。因此 Gateway 可以水平扩展多实例(前置 Nginx 负载均衡),宕掉任何一个实例不影响整体服务。Redis 需要高可用部署(主从哨兵或 Cluster)。

Q:Route 的匹配顺序是什么?

按 Order 排序(越小越先匹配),未配置 Order 时按定义顺序。一旦路由匹配就不再继续匹配,所以范围大的路由(如 /**)要放在最后。


16. 自定义断言工厂

知识点:扩展 Gateway 的路由匹配能力

text 复制代码
场景:根据请求中的 App 版本号路由(只有 v2.0 以上的 APP 才路由到新版接口)

内置断言不满足此需求,需要自定义断言工厂
java 复制代码
/**
 * 自定义断言:根据请求 Header 中的版本号匹配
 * 配置方式:- AppVersion=2.0
 * 效果:只有 Header 中 X-App-Version >= 2.0 的请求才匹配此路由
 */
@Component
public class AppVersionRoutePredicateFactory
        extends AbstractRoutePredicateFactory<AppVersionRoutePredicateFactory.Config> {

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

    @Override
    public List<String> shortcutFieldOrder() {
        return List.of("minVersion");  // 配置文件中参数的顺序
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            String versionHeader = exchange.getRequest()
                .getHeaders().getFirst("X-App-Version");
            if (versionHeader == null) return false;

            try {
                double requestVersion = Double.parseDouble(versionHeader);
                return requestVersion >= config.getMinVersion();
            } catch (NumberFormatException e) {
                return false;
            }
        };
    }

    @Data
    public static class Config {
        private double minVersion;  // 最低版本要求
    }
}
yaml 复制代码
# 使用自定义断言
spring:
  cloud:
    gateway:
      routes:
        - id: new-user-api
          uri: lb://user-service-v2
          predicates:
            - Path=/api/users/**
            - AppVersion=2.0   # 只有 X-App-Version >= 2.0 才路由到新版
          filters:
            - StripPrefix=0

        - id: old-user-api
          uri: lb://user-service-v1
          predicates:
            - Path=/api/users/**  # 兜底路由(旧版本 APP)

17. 自定义过滤器工厂

知识点:复用过滤器逻辑,按路由配置

java 复制代码
/**
 * 自定义过滤器工厂:请求参数解密
 * 某些安全接口要求客户端对请求参数 AES 加密
 * 网关统一解密后,转发明文请求给下游
 *
 * 配置方式:filters: - DecryptRequest=AES-KEY
 */
@Component
public class DecryptRequestGatewayFilterFactory
        extends AbstractGatewayFilterFactory<DecryptRequestGatewayFilterFactory.Config> {

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

    @Override
    public List<String> shortcutFieldOrder() {
        return List.of("key");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 读取并解密请求体
            return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);

                    try {
                        // AES 解密
                        String encrypted = new String(bytes, StandardCharsets.UTF_8);
                        String decrypted = AesUtils.decrypt(encrypted, config.getKey());
                        byte[] decryptedBytes = decrypted.getBytes(StandardCharsets.UTF_8);

                        // 用解密后的内容替换请求体
                        DataBuffer newBuffer = exchange.getResponse().bufferFactory()
                            .wrap(decryptedBytes);
                        ServerHttpRequest newRequest = exchange.getRequest().mutate()
                            .header(HttpHeaders.CONTENT_LENGTH,
                                String.valueOf(decryptedBytes.length))
                            .build();

                        return chain.filter(exchange.mutate()
                            .request(new ServerHttpRequestDecorator(newRequest) {
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    return Flux.just(newBuffer);
                                }
                            }).build());
                    } catch (Exception e) {
                        exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                        return exchange.getResponse().setComplete();
                    }
                });
        };
    }

    @Data
    public static class Config {
        private String key; // AES 解密密钥
    }
}

18. 网关熔断与降级

知识点:Gateway 集成 Resilience4j 熔断

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: CircuitBreaker
              args:
                name: userServiceCB          # 断路器名称(对应 R4j 配置)
                fallbackUri: forward:/fallback/user-service  # 降级地址

# Resilience4j 断路器配置
resilience4j:
  circuitbreaker:
    instances:
      userServiceCB:
        sliding-window-type: COUNT_BASED     # 基于请求次数
        sliding-window-size: 10              # 统计最近10次请求
        minimum-number-of-calls: 5           # 至少5次才触发熔断评估
        failure-rate-threshold: 50           # 失败率 > 50% 触发熔断
        slow-call-duration-threshold: 2s     # 2秒以上算慢调用
        slow-call-rate-threshold: 80         # 慢调用 > 80% 也触发熔断
        wait-duration-in-open-state: 30s     # 熔断后等待 30 秒
        permitted-number-of-calls-in-half-open-state: 3  # 半开状态放3个探测
        automatic-transition-from-open-to-half-open-enabled: true
java 复制代码
// 降级处理 Controller(统一返回友好提示)
@RestController
@RequestMapping("/fallback")
@Slf4j
public class FallbackController {

    @RequestMapping("/user-service")
    public ResponseEntity<Result<Object>> userServiceFallback(
            ServerWebExchange exchange, Throwable cause) {
        String requestPath = exchange.getRequest().getURI().getPath();
        log.warn("用户服务熔断降级,请求路径: {}, 原因: {}",
            requestPath, cause != null ? cause.getMessage() : "unknown");

        // 根据请求类型返回不同的降级数据
        if (requestPath.contains("/profile")) {
            // 返回缓存的用户信息
            return ResponseEntity.ok(Result.fail(503, "用户信息暂时无法获取,请稍后刷新"));
        }

        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
            .body(Result.fail(503, "用户服务维护中,请稍后再试"));
    }

    @RequestMapping("/order-service")
    public ResponseEntity<Result<Object>> orderServiceFallback(Throwable cause) {
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
            .body(Result.fail(503, "订单服务暂时不可用,您的订单不会丢失,请稍后查询"));
    }
}

19. 网关监控与可观测性

知识点:Gateway 关键指标监控

yaml 复制代码
# 开启 Gateway 相关指标
management:
  endpoints:
    web:
      exposure:
        include: "health,prometheus,gateway"
  metrics:
    tags:
      application: gateway-service
    distribution:
      percentiles-histogram:
        spring.cloud.gateway.requests: true  # 开启请求直方图(P99等)
      slo:
        spring.cloud.gateway.requests: 100ms, 500ms, 1s  # SLO 阈值

Gateway 内置 Prometheus 指标

text 复制代码
关键指标:

spring_cloud_gateway_requests_seconds_count
  → 按路由 ID、状态码统计请求数
  
spring_cloud_gateway_requests_seconds_sum
  → 请求耗时总和(可计算平均耗时)

spring_cloud_gateway_requests_seconds_bucket
  → 耗时分布直方图(可计算 P50/P95/P99)
yaml 复制代码
# Prometheus 告警规则
groups:
  - name: gateway-alerts
    rules:
      # 网关 5xx 错误率超过 2%
      - alert: GatewayHighErrorRate
        expr: |
          rate(spring_cloud_gateway_requests_seconds_count{httpStatusCode=~"5.."}[5m])
          /
          rate(spring_cloud_gateway_requests_seconds_count[5m]) > 0.02
        for: 2m
        annotations:
          summary: "网关错误率过高 {{ $value | humanizePercentage }}"

      # 网关 P99 超过 1 秒
      - alert: GatewayHighLatency
        expr: |
          histogram_quantile(0.99,
            rate(spring_cloud_gateway_requests_seconds_bucket[5m])) > 1
        for: 3m
        annotations:
          summary: "网关 P99 延迟超过 1 秒"
相关推荐
北风toto3 小时前
Spring Boot / Spring Cloud 配置文件加密详解:使用 jasypt-spring-boot 实现 ENC() 加密
spring boot·后端·spring cloud
phltxy5 小时前
Spring Cloud 服务注册与发现:Eureka 从原理到实战
java·spring cloud·eureka
phltxy6 小时前
微服务多机部署与负载均衡实战:从手写轮询到 Spring Cloud LoadBalancer 落地
spring cloud·微服务·负载均衡
空中海1 天前
Spring Cloud 专家级面试题库
spring·spring cloud·面试
phltxy1 天前
Spring Cloud入门到实战:微服务架构一站式学习
spring cloud·微服务·架构
身如柳絮随风扬1 天前
Spring Boot + Spring Cloud 集成 Elasticsearch:从零搭建企业级搜索服务
spring boot·elasticsearch·spring cloud
sing~~1 天前
SpringCloud的了解和使用
后端·spring·spring cloud
慕容卡卡1 天前
Claude 使用神器(web页面)--CloudCLI UI
java·开发语言·前端·人工智能·ui·spring cloud
空中海1 天前
Spring Cloud第三篇:通信篇 — OpenFeign 与负载均衡
spring·spring cloud·负载均衡