第五篇:网关篇 — 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 秒"
相关推荐
Rain的Java大神之路6 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
需要8269 小时前
MySQL 索引 B+ 树与“查询为什么变慢了
java·spring boot·spring·spring cloud·tomcat
需要82614 小时前
Arthas 一个命令排查线上问题
java·jvm·spring·spring cloud
苏渡苇2 天前
Spring Insight 里如何把 Span 画成瀑布时间线
后端·spring·spring cloud·springboot·监控·apm
叶总没有会2 天前
Nacos—注册中心
spring cloud·微服务
苏渡苇3 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring·spring cloud·系统监控·apm
叶总没有会3 天前
微服务--分布式基础
java·spring·spring cloud·微服务
孙启超3 天前
【AI开发之Rust】第 7 课:错误处理 —— panic、Result 与 `?`
人工智能·分布式·后端·爬虫·spring cloud·架构·rust
行百里er4 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring cloud·监控
砍材农夫5 天前
spring|spring web|拦截器、过滤器、aop
java·spring boot·spring·spring cloud