目标 :掌握 API 网关的路由、过滤、限流与鉴权
学习时长:1~2 周
目录
- [为什么需要 API 网关](#为什么需要 API 网关)
- [Spring Cloud Gateway 架构](#Spring Cloud Gateway 架构)
- 路由配置详解
- 断言(Predicate)
- 过滤器(Filter)
- 全局过滤器
- 网关鉴权
- 网关限流
- [跨域处理 CORS](#跨域处理 CORS)
- 动态路由
- [Gateway vs Nginx](#Gateway vs Nginx)
- 面试高频题
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 操作的原子性。
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 秒"