Spring Cloud GateWay

46770 字
234 分钟
Spring Cloud GateWay
[1.Spring Cloud Gateway 的核心组件及作用?](#####Spring Cloud Gateway 核心组件及作用)#
[2.Gateway 如何实现限流?Redis 限流的原理是什么?](#####Spring Cloud Gateway 实现限流的方式)#
[3.Gateway 的 Filter 分为哪两类?执行顺序如何控制?](#####Gateway 的 Filter 核心分类)#
[4.什么是 Predicate?常用的 Predicate 有哪些?](#####什么是 Gateway 的 Predicate?)#
5.Gateway 如何实现动态路由?#
6.Gateway 的负载均衡原理?如何自定义负载均衡策略?#
7.Gateway 如何实现权限校验#
[8.Sentinel 和 Redis 限流在 Gateway 中的区别](#####先理清两种限流在 Gateway 中的基础实现)#
[9.Gateway 如何避免服务雪崩?(熔断降级 + 限流)](#####Gateway 如何避免服务雪崩?(熔断降级 + 限流))#
Gateway 如何避免服务雪崩?(熔断降级 + 限流)#

Spring Cloud Gateway 如何通过熔断降级 + 限流组合手段避免服务雪崩,这是网关作为微服务流量入口的核心防护能力 —— 限流从 “入口” 控制总流量,避免后端服务被压垮;熔断降级则在后端服务故障时 “及时切断” 请求,防止故障扩散引发雪崩。我会先讲服务雪崩的成因,再给出基于 Sentinel(一体化支持限流 + 熔断)的完整实现方案,代码可直接落地。

一、先理解:服务雪崩的成因#

当后端某个服务出现故障(如响应超时、异常率飙升、资源耗尽),Gateway 若仍持续向该服务转发请求,会导致:

  1. 网关线程池被大量阻塞请求占满,无法处理其他正常服务的请求;
  2. 故障服务的请求积压,进一步加剧服务不可用;
  3. 故障扩散到上下游服务,最终引发整个微服务集群雪崩。

Gateway 防护核心逻辑

  • 限流:前置防护,控制进入系统的总流量(如单接口 QPS、单 IP 请求数),避免流量超过后端服务承载能力;
  • 熔断降级:故障隔离,当后端服务异常率 / 超时率达到阈值时,暂时 “熔断” 该服务,直接返回降级响应,待服务恢复后再恢复转发,防止故障扩散。

二、完整实现方案:基于 Sentinel 实现限流 + 熔断降级#

Sentinel 是阿里开源的流量治理组件,一站式支持 Gateway 的限流、熔断、降级,是防雪崩的最优选择(无需整合多个组件),具体步骤如下:

步骤 1:引入核心依赖#

<!-- Gateway 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Sentinel 适配 Gateway(核心) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2023.0.1.0</version> <!-- 适配Spring Cloud 2023版本 -->
</dependency>
<!-- Sentinel 核心依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<!-- Sentinel 控制台依赖(可视化配置规则) -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<!-- 可选:Nacos 持久化规则(避免重启丢失) -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

步骤 2:配置 Sentinel 控制台与基础参数#

application.yml 中配置 Sentinel 控制台连接、Gateway 适配参数:

spring:
cloud:
# Gateway 配置(示例路由)
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates: [Path=/user/**]
- id: order-service-route
uri: lb://order-service
predicates: [Path=/order/**]
# Sentinel 配置
alibaba:
sentinel:
# Sentinel 控制台地址
transport:
dashboard: localhost:8080 # 控制台端口(需先启动Sentinel控制台)
port: 8719 # 与控制台通信的端口
# Gateway 适配配置
gateway:
enabled: true # 开启Sentinel对Gateway的支持
scg:
fallback:
mode: response # 降级响应模式(返回自定义JSON)
response-body: "{\"code\":503,\"msg\":\"服务繁忙,请稍后再试\",\"success\":false}"
response-status: 503 # 降级响应状态码
content-type: application/json
# 可选:Nacos 持久化规则(避免重启网关丢失规则)
datasource:
ds1:
nacos:
server-addr: localhost:8848
data-id: gateway-sentinel-rules
group-id: DEFAULT_GROUP
rule-type: gw_flow # 网关限流规则
ds2:
nacos:
server-addr: localhost:8848
data-id: gateway-sentinel-degrade-rules
group-id: DEFAULT_GROUP
rule-type: gw_degrade # 网关熔断降级规则

步骤 3:启动 Sentinel 控制台#

  1. 下载 Sentinel 控制台 jar 包:Sentinel 官方下载
  2. 启动控制台:
java -jar sentinel-dashboard-1.8.7.jar --server.port=8080
  1. 访问控制台:http://localhost:8080(默认账号 / 密码:sentinel/sentinel)。

步骤 4:配置限流规则(前置防护)#

通过 Sentinel 控制台配置网关限流规则,控制单接口 / 单 IP 的请求量:

  1. 进入控制台 → 网关流控 → 新增网关流控规则:

    配置项示例值说明
    资源名user-service-route对应 Gateway 的路由 ID(或接口路径:/user/**)
    限流类型QPS按每秒请求数限流(也可选 “并发数”)
    阈值100每秒最多允许 100 个请求
    流控模式直接直接限流当前资源
    流控效果快速失败超出阈值直接返回降级响应(也可选 “Warm Up” 预热、“匀速排队”)
    限流粒度IP按客户端 IP 限流(也可选手动选择、参数等)

步骤 5:配置熔断降级规则(故障隔离)#

当后端服务异常率 / 超时率达到阈值时,自动熔断该服务,避免持续转发请求:

  1. 进入控制台 → 网关熔断 → 新增网关熔断规则:

    配置项示例值说明
    资源名user-service-route对应 Gateway 的路由 ID
    熔断策略异常比例按异常请求比例熔断(也可选 “异常数”“慢调用比例”)
    阈值0.5异常比例达到 50% 时触发熔断
    熔断时长10熔断后 10 秒内不再转发请求到该服务
    最小请求数5触发熔断的最小请求数(避免少量请求误触发)
    统计时长10统计异常比例的时间窗口(10 秒)
    慢调用阈值500(慢调用比例策略用)响应时间超过 500ms 视为慢调用

步骤 6:自定义降级响应(可选)#

默认降级响应可满足基本需求,若需更灵活的自定义响应,可实现 BlockRequestHandler

import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
/**
* 自定义Sentinel降级响应处理器
*/
@Component
public class CustomSentinelBlockHandler extends SentinelGatewayBlockExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
String msg;
if (ex instanceof BlockException) {
// 限流触发的降级
msg = "{\"code\":429,\"msg\":\"请求过于频繁,请稍后再试\",\"success\":false}";
} else {
// 熔断触发的降级
msg = "{\"code\":503,\"msg\":\"服务暂不可用,请稍后再试\",\"success\":false}";
}
DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
}

三、备选方案:Resilience4j 实现熔断降级(不依赖 Sentinel)#

若不想引入 Sentinel,可使用 Resilience4j(轻量熔断组件)结合 Redis 限流实现防护:

1. 引入 Resilience4j 依赖#

<!-- Resilience4j 熔断依赖 -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-cloud-gateway</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
</dependency>

2. 配置熔断规则(application.yml)#

resilience4j:
circuitbreaker:
instances:
user-service: # 对应路由ID
failureRateThreshold: 50 # 失败率阈值50%
waitDurationInOpenState: 10000 # 熔断时长10秒
minimumNumberOfCalls: 5 # 最小触发请求数
slidingWindowSize: 10 # 统计窗口大小
timelimiter:
instances:
user-service:
timeoutDuration: 1000 # 请求超时时间1秒
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates: [Path=/user/**]
filters:
# 整合Resilience4j熔断
- name: CircuitBreaker
args:
name: user-service
fallbackUri: forward:/fallback/user # 降级转发到本地接口

3. 实现降级接口#

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class FallbackController {
@RequestMapping("/fallback/user")
public Mono<String> userFallback() {
return Mono.just("{\"code\":503,\"msg\":\"用户服务暂不可用\",\"success\":false}");
}
}

四、关键优化点(避免雪崩的核心细节)#

  1. 设置合理的超时时间:Gateway 转发请求时设置超时(如 1 秒),避免线程长时间阻塞:
spring:
cloud:
gateway:
httpclient:
connect-timeout: 500 # 连接超时500ms
response-timeout: 1000 # 响应超时1秒
  1. 降级策略差异化:核心接口(如支付)返回 “排队提示”,非核心接口(如商品列表)返回 “缓存数据”,提升用户体验;
  2. 监控告警:通过 Sentinel 控制台 / Prometheus 监控限流 / 熔断指标,异常时及时告警(如熔断触发、QPS 突增);
  3. 限流粒度精细化:对热点接口(如秒杀)按 “用户 ID + 商品 ID” 限流,避免单一用户刷爆接口;
  4. 预热限流:对新上线服务使用 “Warm Up” 流控效果,避免刚启动就承受满流量。

总结#

  1. Gateway 避免服务雪崩的核心是

    限流(前置控流量)+ 熔断降级(故障隔离)

    双管齐下:

    • 限流:控制进入系统的总流量,避免后端服务过载;
    • 熔断降级:后端服务故障时切断请求,防止故障扩散;
  2. 优先选择 Sentinel 实现一体化防护(限流 + 熔断 + 监控),轻量场景可选用 Resilience4j + Redis 限流;

  3. 关键细节:合理设置超时时间、降级策略、监控告警,确保防护规则适配业务场景。

通过这套方案,能有效隔离故障服务、控制流量峰值,从入口层杜绝服务雪崩的发生。

先理清两种限流在 Gateway 中的基础实现#

在对比前,先简单回顾两种方案的核心实现方式,帮你建立基础认知:

  • Redis 限流:基于 Gateway 官方的 RequestRateLimiterGatewayFilterFactory,核心是令牌桶算法 + Redis Lua 脚本(保证原子性),依赖 Redis 存储令牌状态,功能聚焦 “纯限流”。
  • Sentinel 限流:基于 Sentinel 对 Gateway 的适配组件(spring-cloud-alibaba-sentinel-gateway),核心是 Sentinel 核心库的多限流算法(令牌桶 / 漏桶 / 预热等),支持限流、熔断、降级一体化,可本地嵌入式运行或结合 Sentinel 控制台管理。

二、Sentinel vs Redis 限流(Gateway 中)核心区别#

以下是最关键的维度对比,清晰展示两者的差异:

对比维度Redis 限流(Gateway 官方)Sentinel 限流(Alibaba)
核心定位单一功能:仅实现限流(令牌桶)全栈流量治理:限流 + 熔断 + 降级 + 热点限流 + 系统保护
底层算法仅支持令牌桶算法(固定速率 + 突发容量)支持多种算法:令牌桶、漏桶、预热限流、匀速排队、热点参数限流等
限流维度基础维度:IP、用户 ID、接口路径(需自定义 KeyResolver)丰富维度:路径、请求参数、Header、来源 IP、热点参数(如商品 ID)、系统维度(QPS/CPU)等
依赖组件必须依赖 Redis 服务(网络调用)无强制外部依赖:规则可本地存储;可选 Sentinel 控制台(可视化)
监控与可视化无原生监控,需自行开发(如 Redis 监控令牌数、埋点统计)自带 Sentinel 控制台:实时查看限流指标、修改规则、查看熔断状态、链路追踪
动态规则需修改配置中心(Nacos/Redis)+ 触发路由刷新控制台实时修改规则,无需重启网关;支持规则持久化到 Nacos/Apollo
熔断降级无原生支持,需自行结合 Resilience4j 等实现原生支持:基于异常比例 / 异常数 / 慢调用比例熔断后端服务
性能有网络开销(调用 Redis),高并发下略逊本地限流(无网络调用),性能更高;规则拉取仅首次 / 变更时触发
规则粒度路由级 / 全局级(粗粒度)接口级 / 参数级(细粒度,如对 /order/{id} 中 id=1001 的请求单独限流)
异常处理需自定义返回结果(429)自带降级返回策略,支持自定义限流 / 熔断响应(JSON / 跳转)

三、关键区别拆解(通俗易懂版)#

1. 核心定位:“单一工具” vs “全栈平台”#

  • Redis 限流:就像 “一把专门的限流锁”,只解决 “控制请求数量” 的问题,功能单一,没有其他附加能力。
  • Sentinel:就像 “流量治理管家”,除了限流,还能监控请求、熔断故障服务(避免雪崩)、降级非核心接口、限制热点参数请求,是一站式解决方案。

2. 性能:“远程调用” vs “本地计算”#

  • Redis 限流:每次请求都要调用 Redis 执行 Lua 脚本(网络 IO),高并发下会有一定的网络开销;
  • Sentinel 限流:限流规则加载到本地内存,每次请求直接本地计算是否限流(无网络调用),性能更高,仅在规则变更时从控制台 / 配置中心拉取新规则。

3. 功能丰富度:“基础限流” vs “精细化治理”#

举两个典型场景:

  • 场景 1:热点参数限流

    想限制 “商品详情接口(/goods/{id})中,id=1001 的爆款商品请求不超过 100 QPS”——Redis 限流做不到(只能按路径 / IP 限流),Sentinel 可通过 “热点参数限流” 轻松实现。

  • 场景 2:服务熔断

    后端用户服务故障(异常率 > 50%),想暂时熔断该服务,返回兜底响应 ——Redis 限流无此能力,Sentinel 可基于异常比例自动熔断,避免网关持续转发请求到故障服务。

4. 运维成本:“需自研监控” vs “可视化控制台”#

  • Redis 限流:限流效果、令牌剩余数、触发次数等指标,需要你自己写监控脚本(如从 Redis 读取 key 统计)、 Grafana 绘图;
  • Sentinel 控制台:开箱即用,能看到每个接口的 QPS、限流次数、熔断状态,还能实时修改限流规则(比如临时把 QPS 从 100 调整为 200),无需重启网关。

四、选型建议(帮你快速决策)#

选择 Redis 限流选择 Sentinel 限流
场景简单:仅需基础限流(IP / 接口)场景复杂:需精细化限流 + 熔断 + 降级 + 监控
已有 Redis 集群,不想引入新组件追求低性能开销、可视化运维、全栈流量治理
网关流量适中,对网络开销不敏感高并发场景(如秒杀),要求本地限流、低延迟
仅需 “限流” 单一功能,无熔断需求微服务架构,需保护后端服务(熔断降级)

总结#

  1. Redis 限流是轻量、单一功能的方案,依赖 Redis,适合简单限流场景,已有 Redis 集群时接入成本低;
  2. Sentinel 是功能全面、性能更优的流量治理方案,无强制外部依赖,支持限流、熔断、监控一体化,适合复杂微服务场景;
  3. 核心决策点:是否需要熔断 / 降级 / 精细化限流 / 可视化监控 —— 需要则选 Sentinel,仅基础限流则选 Redis。

Gateway 权限校验的核心原理#

Gateway 本身不提供内置的权限校验功能,但可通过 GlobalFilter(全局过滤器) 实现全量请求拦截,在请求转发到后端服务的 pre 阶段完成权限校验,核心流程:

无效

有效

客户端发送请求到Gateway

GlobalFilter拦截请求

是否在白名单?

直接放行请求

提取身份凭证(Token/JWT)

校验凭证有效性?

返回401 Unauthorized

解析用户信息,校验接口访问权限

是否有权限?

返回403 Forbidden

放行请求到后端服务

</svg

graph TD
A[客户端发送请求到Gateway] --> B[GlobalFilter拦截请求]
B --> C{是否在白名单?}
C -- 是 --> D[直接放行请求]
C -- 否 --> E[提取身份凭证(Token/JWT)]
E --> F{校验凭证有效性?}
F -- 无效 --> G[返回401 Unauthorized]
F -- 有效 --> H[解析用户信息,校验接口访问权限]
H --> I{是否有权限?}
I -- 无 --> J[返回403 Forbidden]
I -- 有 --> K[放行请求到后端服务]

无效

有效

客户端发送请求到Gateway

GlobalFilter拦截请求

是否在白名单?

直接放行请求

提取身份凭证(Token/JWT)

校验凭证有效性?

返回401 Unauthorized

解析用户信息,校验接口访问权限

是否有权限?

返回403 Forbidden

放行请求到后端服务

二、实操实现:权限校验完整方案#

以下是最常用的 JWT Token 身份校验 + 接口权限校验 实现步骤,适配绝大多数业务场景:

步骤 1:引入核心依赖#

<!-- Gateway 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- JWT 工具依赖(用于Token生成/解析) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- 可选:Redis依赖(存储黑名单Token/用户权限) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

步骤 2:编写 JWT 工具类(Token 生成 / 校验)#

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Map;
/**
* JWT工具类:生成、解析、验证Token
*/
@Component
public class JwtUtil {
// JWT密钥(建议配置在Nacos/配置文件,长度至少256位)
@Value("${jwt.secret:abcdefghijklmnopqrstuvwxyz1234567890abcdef}")
private String secret;
// Token过期时间(单位:毫秒,示例:2小时)
@Value("${jwt.expire:7200000}")
private long expire;
// 生成Token
public String generateToken(String userId, Map<String, Object> claims) {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
return Jwts.builder()
.setClaims(claims) // 自定义载荷(如角色、权限)
.setSubject(userId) // 主题(用户ID)
.setIssuedAt(new Date()) // 签发时间
.setExpiration(new Date(System.currentTimeMillis() + expire)) // 过期时间
.signWith(key, SignatureAlgorithm.HS256) // 签名算法
.compact();
}
// 解析Token,获取载荷信息
public Claims parseToken(String token) {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
// 验证Token是否有效(过期/签名错误)
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (ExpiredJwtException e) {
// Token过期
return false;
} catch (MalformedJwtException | SignatureException | IllegalArgumentException e) {
// 签名错误/Token格式错误/空Token
return false;
}
}
}

步骤 3:配置白名单(无需校验的接口)#

import org.springframework.context.annotation.Configuration;
import java.util.HashSet;
import java.util.Set;
/**
* 权限白名单配置:登录、注册、健康检查等接口无需校验
*/
@Configuration
public class AuthWhiteListConfig {
// 白名单路径(支持通配符**)
public static final Set<String> WHITE_LIST = new HashSet<>();
static {
// 登录/注册接口
WHITE_LIST.add("/auth/login");
WHITE_LIST.add("/auth/register");
// 健康检查接口
WHITE_LIST.add("/actuator/**");
// 公开接口
WHITE_LIST.add("/public/**");
}
}

步骤 4:自定义权限校验 GlobalFilter(核心)#

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
/**
* 全局权限校验过滤器:Token校验 + 接口权限校验
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Resource
private JwtUtil jwtUtil;
// 路径匹配器(支持通配符)
private final AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().toString();
// 1. 判断是否在白名单,白名单接口直接放行
if (isWhiteList(path)) {
return chain.filter(exchange);
}
// 2. 提取Token(从请求头Authorization中获取,格式:Bearer xxx)
List<String> authHeaders = request.getHeaders().get("Authorization");
if (authHeaders == null || authHeaders.isEmpty()) {
// 无Token,返回401
return responseError(exchange, HttpStatus.UNAUTHORIZED, "未登录,请先登录");
}
String token = authHeaders.get(0).replace("Bearer ", "");
// 3. 校验Token有效性
if (!jwtUtil.validateToken(token)) {
return responseError(exchange, HttpStatus.UNAUTHORIZED, "Token无效或已过期");
}
// 4. 解析Token,获取用户信息和权限
Map<String, Object> claims = jwtUtil.parseToken(token);
String userId = claims.get("sub").toString(); // 用户ID
List<String> userPermissions = (List<String>) claims.get("permissions"); // 用户权限列表
// 5. 校验接口访问权限(示例:路径=/user/add 对应权限码user:add)
String requiredPermission = getPermissionByPath(path);
if (requiredPermission != null && !userPermissions.contains(requiredPermission)) {
// 无接口权限,返回403
return responseError(exchange, HttpStatus.FORBIDDEN, "无接口访问权限");
}
// 6. 将用户信息放入请求头,供后端服务使用
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", userId)
.header("X-User-Permissions", String.join(",", userPermissions))
.build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
// 7. 权限校验通过,放行请求
return chain.filter(newExchange);
}
// 判断路径是否在白名单
private boolean isWhiteList(String path) {
return AuthWhiteListConfig.WHITE_LIST.stream()
.anyMatch(whitePath -> pathMatcher.match(whitePath, path));
}
// 自定义:路径转换为权限码(根据业务规则调整)
private String getPermissionByPath(String path) {
// 示例规则:/user/add → user:add;/order/list → order:list
if (pathMatcher.match("/user/add", path)) {
return "user:add";
} else if (pathMatcher.match("/user/delete/**", path)) {
return "user:delete";
} else if (pathMatcher.match("/order/list", path)) {
return "order:list";
}
// 无需权限的接口返回null
return null;
}
// 统一返回权限错误响应(JSON格式)
private Mono<Void> responseError(ServerWebExchange exchange, HttpStatus status, String msg) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(status);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
String errorJson = String.format("{\"code\":%d,\"msg\":\"%s\",\"success\":false}", status.value(), msg);
return response.writeWith(Mono.just(
response.bufferFactory().wrap(errorJson.getBytes(StandardCharsets.UTF_8))
));
}
// 设置过滤器优先级(数值越小越先执行,建议比限流过滤器高)
@Override
public int getOrder() {
return -100;
}
}

步骤 5:配置 JWT 相关参数(application.yml)#

yaml

# JWT配置
jwt:
secret: abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz123456
expire: 7200000 # Token有效期2小时
# Gateway路由配置(示例)
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/user/**
- id: auth-service-route
uri: lb://auth-service
predicates:
- Path=/auth/**

三、进阶优化(可选)#

  1. Token 黑名单:若用户登出 / Token 被盗,需将 Token 加入 Redis 黑名单,在 validateToken 中增加黑名单校验;
  2. 分布式权限缓存:将用户权限列表缓存到 Redis,避免每次解析 Token(适合权限频繁变更的场景);
  3. 细粒度角色校验:在过滤器中增加角色校验(如 admin 角色可访问所有接口);
  4. 限流 + 权限联动:权限校验通过后,再执行限流逻辑(调整过滤器 order 值,权限过滤器 order 更小)。

总结#

  1. Gateway 权限校验核心是GlobalFilter,在 pre 阶段拦截请求,优先校验白名单,再做 Token 身份认证和接口权限校验;
  2. 基础校验需实现 JWT 工具类(Token 生成 / 解析)+ 白名单配置 + 全局过滤器;
  3. 校验失败需返回标准化 JSON 响应(401 未登录 / 403 无权限),而非默认空响应,提升前端适配性。

关键点:过滤器 order 值需合理设置(建议 -100 左右),确保权限校验优先于限流、路由转发等过滤器执行。

Gateway 负载均衡的核心原理#

Gateway 本身不直接实现负载均衡逻辑,而是集成 Spring Cloud LoadBalancer(SCLB)(Spring Cloud 官方替换 Ribbon 的负载均衡组件),结合服务发现(Nacos/Eureka 等)实现请求的负载分发,核心原理可拆解为以下步骤:

1. 核心依赖(自动集成)#

只要引入 Gateway 和服务发现依赖(如 Nacos/Eureka),Spring Cloud 会自动引入 spring-cloud-starter-loadbalancer 依赖,无需手动添加:

<!-- Gateway 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos 服务发现(触发 LoadBalancer 自动装配) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2. 负载均衡核心流程#

客户端发送请求到Gateway

Gateway识别uri前缀为lb://(如lb://user-service)

通过服务发现组件(Nacos)获取user-service的所有实例列表(IP+端口)

Spring Cloud LoadBalancer根据策略(默认轮询)选择一个实例

Gateway将uri替换为选中实例的真实地址(如http://192.168.1.100:8080)

Gateway转发请求到选中的后端实例

后端实例返回响应,Gateway透传给客户端

graph TD
A[客户端发送请求到Gateway] --> B[Gateway识别uri前缀为lb://(如lb://user-service)]
B --> C[通过服务发现组件(Nacos)获取user-service的所有实例列表(IP+端口)]
C --> D[Spring Cloud LoadBalancer根据策略(默认轮询)选择一个实例]
D --> E[Gateway将uri替换为选中实例的真实地址(如http://192.168.1.100:8080)]
E --> F[Gateway转发请求到选中的后端实例]
F --> G[后端实例返回响应,Gateway透传给客户端]

客户端发送请求到Gateway

Gateway识别uri前缀为lb://(如lb://user-service)

通过服务发现组件(Nacos)获取user-service的所有实例列表(IP+端口)

Spring Cloud LoadBalancer根据策略(默认轮询)选择一个实例

Gateway将uri替换为选中实例的真实地址(如http://192.168.1.100:8080)

Gateway转发请求到选中的后端实例

后端实例返回响应,Gateway透传给客户端

3. 关键组件说明#

  • lb:// 前缀:Gateway 识别该前缀时,会触发负载均衡逻辑(而非直接转发到固定地址);
  • ReactiveLoadBalancer:SCLB 为响应式场景(Gateway 基于 WebFlux)提供的负载均衡器接口,默认实现是 RoundRobinLoadBalancer(轮询策略);
  • ServiceInstanceListSupplier:负责从服务发现组件获取目标服务的实例列表(如从 Nacos 拉取 user-service 的所有实例);
  • LoadBalancerClientFilter:Gateway 内置的全局过滤器(order=0),核心作用是将 lb:// 前缀的 uri 替换为选中实例的真实地址。

二、自定义负载均衡策略(实操步骤)#

SCLB 默认提供轮询(RoundRobin)随机(Random) 策略,实际场景中常需自定义(如权重、最少连接数、IP 哈希等),以下以权重策略为例,讲解完整实现步骤:

步骤 1:排除默认策略(可选,避免冲突)#

若需全局替换默认策略,可在启动类排除默认配置:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;
// 排除默认的负载均衡自动配置(可选)
@SpringBootApplication(exclude = LoadBalancerAutoConfiguration.class)
// 指定自定义策略的配置类(全局生效)
@LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfig.class)
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

步骤 2:自定义负载均衡策略类(权重策略)#

实现 ReactorServiceInstanceLoadBalancer 接口,适配响应式场景:

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.core.env.Environment;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* 自定义权重负载均衡策略
* 要求后端实例的metadata中配置weight(如:10、20、30)
*/
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
// 服务名称
private final String serviceId;
// 实例列表供应商
private ServiceInstanceListSupplier serviceInstanceListSupplier;
// 随机数生成器
private final Random random = new Random();
public WeightedLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
// 获取当前服务ID
this.serviceId = loadBalancerClientFactory.getName(environment);
// 获取实例列表供应商
this.serviceInstanceListSupplier = loadBalancerClientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class);
}
@Override
public Mono<ServiceInstance> choose(Request request) {
// 1. 获取所有可用实例列表
return serviceInstanceListSupplier.get()
.filter(instances -> !instances.isEmpty())
.flatMap(instances -> {
// 2. 解析每个实例的权重,计算总权重
List<WeightedInstance> weightedInstances = instances.stream()
.map(instance -> {
// 从实例metadata中获取权重(默认1)
String weightStr = instance.getMetadata().getOrDefault("weight", "1");
int weight = Integer.parseInt(weightStr);
return new WeightedInstance(instance, weight);
})
.collect(Collectors.toList());
// 3. 计算总权重
int totalWeight = weightedInstances.stream().mapToInt(WeightedInstance::getWeight).sum();
if (totalWeight <= 0) {
return Mono.empty();
}
// 4. 随机生成一个0~totalWeight-1的数,按权重区间选择实例
int randomWeight = random.nextInt(totalWeight);
int currentWeight = 0;
for (WeightedInstance weightedInstance : weightedInstances) {
currentWeight += weightedInstance.getWeight();
if (randomWeight < currentWeight) {
return Mono.just(weightedInstance.getInstance());
}
}
// 兜底:返回第一个实例
return Mono.just(weightedInstances.get(0).getInstance());
});
}
// 内部类:封装实例和权重
private static class WeightedInstance {
private final ServiceInstance instance;
private final int weight;
public WeightedInstance(ServiceInstance instance, int weight) {
this.instance = instance;
this.weight = weight;
}
public ServiceInstance getInstance() {
return instance;
}
public int getWeight() {
return weight;
}
}
}

步骤 3:配置自定义策略(绑定到服务)#

创建配置类,将自定义策略注册为 Bean:

import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* 负载均衡策略配置类
*/
@Configuration
public class CustomLoadBalancerConfig {
/**
* 注册自定义权重策略
*/
@Bean
public ReactorServiceInstanceLoadBalancer weightedLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
return new WeightedLoadBalancer(environment, loadBalancerClientFactory);
}
/**
* 配置实例列表供应商(默认即可,无需修改)
*/
@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier(LoadBalancerClientFactory factory) {
return factory.getLazyProvider(factory.getName(environment), ServiceInstanceListSupplier.class);
}
}

步骤 4:配置后端服务实例权重(以 Nacos 为例)#

在 Nacos 控制台为 user-service 的不同实例配置 metadata

  1. 进入 Nacos 控制台 → 服务管理 → 服务列表 → 点击 user-service
  2. 编辑实例的元数据,添加 weight: 10weight: 20weight: 30(权重越高,被选中概率越大)。

步骤 5:测试自定义策略#

  1. 启动网关和多个 user-service 实例(配置不同权重);
  2. 多次访问 http://网关IP:网关端口/user/xxx
  3. 查看后端实例的日志,验证权重高的实例接收的请求数更多(如权重 30 的实例接收请求数约为权重 10 的 3 倍)。

三、其他自定义策略场景#

策略类型适用场景核心实现思路
最少连接数策略后端实例性能差异大记录每个实例的当前连接数,选择连接数最少的实例
IP 哈希策略保证同一客户端请求到同一实例对客户端 IP 做哈希运算,映射到固定实例(解决会话粘滞问题)
区域优先策略多机房部署优先选择与网关同区域的实例,跨区域实例作为兜底

总结#

  1. Gateway 负载均衡依赖 Spring Cloud LoadBalancer,通过 lb:// 前缀触发,核心流程是「获取实例列表 → 按策略选实例 → 替换 uri 转发」;
  2. 自定义负载均衡策略需实现 ReactorServiceInstanceLoadBalancer 接口,适配 Gateway 的响应式架构;
  3. 权重策略是最常用的自定义场景,需在后端实例的 metadata 中配置权重,在策略类中按权重区间选择实例。

关键点:自定义策略需适配响应式模型(使用 Mono/Flux),避免阻塞操作,确保 Gateway 的高性能。

动态路由的核心原理#

Gateway 的路由规则默认从本地配置文件(application.yml)加载(静态路由),由 PropertiesRouteDefinitionRepository 管理,修改后需重启网关生效。

动态路由的核心思路

  1. 替换默认的 RouteDefinitionRepository 接口实现(该接口负责路由规则的增、删、改、查);
  2. 将路由规则存储到外部配置源(Nacos/Redis/MySQL 等);
  3. 监听外部配置源的变更,触发 Gateway 发布 RefreshRoutesEvent 事件,实时刷新路由规则。

简单来说:动态路由 = 外部配置源存储路由规则 + 自定义路由仓库 + 配置变更监听 + 路由刷新事件。

二、实战实现:基于 Nacos 的动态路由(推荐)#

Nacos 是 Spring Cloud 生态中主流的配置中心,支持配置实时推送,是实现动态路由的首选方案,具体步骤如下:

1. 引入核心依赖#

xml

<!-- Gateway 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos 配置中心依赖(支持配置动态推送) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Nacos 服务发现(可选,配合负载均衡) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2. 配置 Nacos 连接(bootstrap.yml)#

Nacos 配置需放在 bootstrap.yml(优先于 application.yml 加载):

yaml

spring:
application:
name: gateway-service # 网关服务名(对应Nacos配置Data ID)
cloud:
# Nacos 配置中心配置
nacos:
config:
server-addr: localhost:8848 # Nacos 服务地址
file-extension: yaml # 配置文件格式(yaml/yml/properties)
group: DEFAULT_GROUP # 配置分组
namespace: public # 命名空间(默认public)
# Nacos 服务发现配置(可选)
discovery:
server-addr: localhost:8848

3. 自定义动态路由仓库(核心)#

实现 RouteDefinitionRepository 接口,从 Nacos 读取路由规则,并监听配置变更触发路由刷新:

java运行

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.List;
@Component
public class NacosRouteDefinitionRepository implements RouteDefinitionRepository, ApplicationEventPublisherAware {
// Nacos 配置管理器(读取配置)
private final NacosConfigManager nacosConfigManager;
// 路由配置的Data ID(格式:${spring.application.name}.${file-extension})
private final String dataId = "gateway-service.yaml";
// 配置分组
private final String group = "DEFAULT_GROUP";
// 应用事件发布器(发布路由刷新事件)
private ApplicationEventPublisher publisher;
public NacosRouteDefinitionRepository(NacosConfigManager nacosConfigManager) {
this.nacosConfigManager = nacosConfigManager;
}
// 初始化:监听Nacos配置变更
@PostConstruct
public void init() {
try {
// 监听Nacos配置变更,触发路由刷新
nacosConfigManager.getConfigService().addListener(dataId, group, event -> {
// 发布路由刷新事件,Gateway会重新加载路由
publisher.publishEvent(new org.springframework.cloud.gateway.event.RefreshRoutesEvent(this));
});
} catch (Exception e) {
throw new RuntimeException("Nacos路由配置监听失败", e);
}
}
// 读取所有路由规则(Gateway启动/刷新时调用)
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
try {
// 从Nacos读取配置内容
String configContent = nacosConfigManager.getConfigService().getConfig(dataId, group, 5000);
// 将JSON/YAML转为RouteDefinition列表(FastJSON解析)
List<RouteDefinition> routeDefinitions = JSON.parseObject(
configContent,
new TypeReference<List<RouteDefinition>>() {}
);
return Flux.fromIterable(routeDefinitions);
} catch (Exception e) {
return Flux.error(new NotFoundException("从Nacos加载路由规则失败:" + e.getMessage()));
}
}
// 新增路由(按需实现,可调用Nacos API写入配置)
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
// 实际场景可实现:将新增的路由写入Nacos配置
return route.flatMap(r -> {
// 省略:读取现有配置 → 添加新路由 → 调用Nacos API更新配置
return Mono.empty();
});
}
// 删除路由(按需实现)
@Override
public Mono<Void> delete(Mono<String> routeId) {
// 实际场景可实现:读取现有配置 → 删除指定路由 → 调用Nacos API更新配置
return routeId.flatMap(id -> Mono.empty());
}
// 设置事件发布器
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}

4. 在 Nacos 配置中心添加路由规则#

登录 Nacos 控制台 → 配置管理 → 配置列表 → 新建配置:

  • Data ID:gateway-service.yaml(与代码中一致)
  • Group:DEFAULT_GROUP
  • 配置格式:YAML
  • 配置内容(路由规则示例):

yaml

[
{
"id": "user-service-route",
"uri": "lb://user-service",
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/user/**"
}
},
{
"name": "Method",
"args": {
"_genkey_0": "GET"
}
}
],
"filters": [
{
"name": "AddRequestHeader",
"args": {
"_genkey_0": "X-From-Gateway",
"_genkey_1": "Nacos-Dynamic-Route"
}
}
]
},
{
"id": "order-service-route",
"uri": "lb://order-service",
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/order/**"
}
}
]
}
]

5. 测试动态路由#

  1. 启动网关服务,验证路由规则是否从 Nacos 加载;
  2. 在 Nacos 控制台修改路由规则(比如新增 / 删除路由、修改 Predicate/Filter);
  3. 无需重启网关,直接访问测试,验证路由规则已实时生效。

三、其他动态路由实现方式#

除了 Nacos,还可基于 Redis/MySQL 实现,核心思路一致(替换路由仓库 + 监听配置变更):

1. 基于 Redis 实现#

  • 路由规则以 JSON 格式存储在 Redis 的 Hash/List 中;
  • 自定义 RouteDefinitionRepository,从 Redis 读取路由规则;
  • 监听 Redis Key 过期 / 变更事件(或通过消息通知),触发路由刷新。

2. 基于 MySQL 实现#

  • 建表存储路由规则(字段:id、uri、predicates、filters、order 等);
  • 自定义 RouteDefinitionRepository,从数据库读取路由规则;
  • 通过定时任务轮询数据库(简单)或基于 Binlog 监听表变更(实时),触发路由刷新。

四、总结#

  1. Gateway 动态路由的核心是替换 RouteDefinitionRepository,将路由源从本地配置改为外部存储(Nacos/Redis/MySQL);
  2. Nacos 是 Spring Cloud 生态首选,配置简单且支持配置自动推送,无需额外实现监听逻辑;
  3. 路由变更后需发布 RefreshRoutesEvent 事件,让 Gateway 实时感知并重新加载路由规则;
  4. 按需实现 save/delete 方法,可通过 API 动态增删路由,无需手动修改配置中心内容。
什么是 Gateway 的 Predicate?#

Gateway 中的 Predicate(断言)本质是基于 Java 8 java.util.function.Predicate 接口实现的路由匹配条件,核心作用是:

  • 当客户端请求进入网关时,网关会遍历所有路由的 Predicate 集合,判断请求是否满足该路由的所有 Predicate 条件;
  • 只有所有 Predicate 都返回 true,请求才会被该路由匹配,并转发到路由指定的目标 URI;
  • 特点:支持多个 Predicate 组合(逻辑与),也可自定义 Predicate 满足个性化匹配需求(比如按请求体内容匹配)。

简单来说,Predicate 就是网关的 “路由筛选规则”—— 符合规则的请求走这条路由,不符合的跳过。

二、常用的 Predicate 类型及实战示例#

Gateway 内置了十余种常用 Predicate,覆盖请求路径、方法、参数、IP、时间等维度,以下是最常用的类型(均以 application.yml 配置为例):

Predicate 类型核心作用配置示例说明
Path匹配请求路径(最常用)```yaml
predicates:
  • Path=/user/

    ,/order/

    | 匹配所有以 `/user/` 或 `/order/` 开头的请求;`**` 表示多级路径通配,`*` 表示单级路径通配 | | `Method` | 匹配 HTTP 请求方法 |

    yaml

    predicates:

  • Method=GET,POST

| `Header` | 匹配请求头参数 | ```yaml
predicates:
- Header=X-Token, \d+
``` | 匹配请求头中包含 `X-Token` 且值为数字的请求;第二个参数支持正则表达式 |
| `Query` | 匹配 URL 请求参数 | ```yaml
predicates:
- Query=userId, 100[1-9]
- Query=token # 仅判断参数存在,不校验值
``` | 第一个示例:匹配包含 `userId` 参数且值为 1001-1009 的请求;第二个示例:仅判断 `token` 参数存在即可 |
| `RemoteAddr` | 匹配客户端 IP 地址 | ```yaml
predicates:
- RemoteAddr=192.168.1.0/24,10.0.0.0/8
``` | 匹配来自 192.168.1.x 网段或 10.x.x.x 网段的客户端请求;支持 CIDR 格式 |
| `After` | 匹配指定时间**之后**的请求 | ```yaml
predicates:
- After=2026-01-01T00:00:00+08:00[Asia/Shanghai]
``` | 仅匹配 2026年1月1日 0点之后的请求;时间格式为 `ISO-8601`,需指定时区 |
| `Before` | 匹配指定时间**之前**的请求 | ```yaml
predicates:
- Before=2026-12-31T23:59:59+08:00[Asia/Shanghai]
``` | 仅匹配 2026年12月31日 23:59:59 之前的请求 |
| `Between` | 匹配指定时间区间内的请求 | ```yaml
predicates:
- Between=2026-01-01T00:00:00+08:00[Asia/Shanghai],2026-12-31T23:59:59+08:00[Asia/Shanghai]
``` | 仅匹配 2026 全年的请求 |
| `Cookie` | 匹配客户端 Cookie | ```yaml
predicates:
- Cookie=username, zhangsan
``` | 匹配客户端 Cookie 中包含 `username` 且值为 `zhangsan` 的请求;支持正则 |
#### 补充:多 Predicate 组合使用(逻辑与)
实际场景中,常组合多个 Predicate 实现精准匹配,比如:
```yaml
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/user/** # 路径匹配
- Method=GET # 方法匹配
- Header=X-Token, \d+ # 请求头匹配
- RemoteAddr=192.168.1.0/24 # IP匹配

上述配置表示:只有同时满足 “路径以 /user/ 开头 + GET 方法 + 请求头 X-Token 为数字 + 客户端 IP 在 192.168.1.x 网段” 的请求,才会被该路由处理。

三、总结#

  1. Gateway 的 Predicate 是路由的匹配条件,基于 Java 8 Predicate 接口,只有满足所有 Predicate 的请求才会被对应路由转发;
  2. 常用 Predicate 覆盖路径(Path)、方法(Method)、参数(Query)、IP(RemoteAddr)、时间(After/Before) 等核心维度,是配置路由规则的基础;
  3. 多 Predicate 组合为逻辑与关系,可实现精准的请求匹配,满足不同业务场景的路由需求。
Gateway 的 Filter 核心分类#

Gateway 的 Filter 主要分为 GatewayFilter(路由级别过滤器)GlobalFilter(全局级别过滤器) 两类,两者在作用范围、使用方式上有明确区别,具体对比如下:

过滤器类型核心定义作用范围配置 / 实现方式典型使用场景
GatewayFilter路由专属过滤器仅作用于指定 / 部分路由1. 配置文件中通过 filters 节点配置(内置 GatewayFilter);2. 代码自定义 GatewayFilterFactory为特定路由添加请求头、去除路径前缀、路由级限流 / 重试、修改响应头
GlobalFilter全局通用过滤器作用于所有路由1. 实现 GlobalFilter + Ordered 接口;2. 结合 @Order 注解全局身份认证(Token 校验)、全局日志记录、跨域处理(CORS)、全局限流 / 熔断

两类过滤器的简单示例#

1. GatewayFilter(路由级,配置文件方式)#

yaml

spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates: [Path=/user/**]
filters:
# 内置 GatewayFilter:添加请求头(仅作用于该路由)
- AddRequestHeader=X-Route-Id, user-service-route
# 自定义 GatewayFilter:指定 order(优先级)
- name: CustomGatewayFilter
args:
order: 10
2. GlobalFilter(全局级,代码实现)#

java运行

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
// 全局过滤器:记录所有请求的路径(作用于所有路由)
@Component
public class LogGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
System.out.println("全局过滤器:请求路径 = " + request.getPath());
// 继续执行过滤器链
return chain.filter(exchange);
}
// 设置 order(数值越小,优先级越高)
@Override
public int getOrder() {
return 5;
}
}

二、Filter 执行顺序的控制规则#

Gateway 过滤器的执行顺序核心由 order 属性 控制,同时区分「请求转发前(pre 阶段)」和「响应返回后(post 阶段)」两个执行阶段,具体规则如下:

1. 核心规则#

  • order 数值越小,优先级越高:不管是 GatewayFilter 还是 GlobalFilter,order 数值越小,越先执行;
  • pre 阶段(请求转发前):过滤器按 order 升序执行(小 → 大);
  • post 阶段(响应返回后):过滤器按 order 降序执行(大 → 小);
  • 混合执行:GatewayFilter 和 GlobalFilter 会被合并到同一个过滤器链中,按 order 统一排序执行。

2. 不同过滤器设置 order 的方式#

(1)GatewayFilter 设置 order#
  • 内置 GatewayFilter

    :配置文件中通过

    order

    参数指定(默认值一般为

    0

    );yaml

    filters:
    - name: RequestRateLimiter # 内置限流过滤器
    args:
    order: 1 # 设为1,优先级高于默认0的过滤器
    key-resolver: "#{@ipKeyResolver}"
  • 自定义 GatewayFilter

    :通过

    GatewayFilterFactory

    实现时,重写

    getOrder()

    方法:

    java运行

    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.GatewayFilterFactory;
    import org.springframework.core.Ordered;
    import org.springframework.stereotype.Component;
    @Component
    public class CustomGatewayFilterFactory implements GatewayFilterFactory<Object>, Ordered {
    @Override
    public GatewayFilter apply(Object config) {
    return (exchange, chain) -> {
    System.out.println("路由级过滤器执行");
    return chain.filter(exchange);
    };
    }
    // 设置 order
    @Override
    public int getOrder() {
    return 10;
    }
    }
(2)GlobalFilter 设置 order#

有两种方式(Ordered 接口优先级高于 @Order 注解):

  • 方式 1:实现 Ordered 接口

    (推荐,更直观):

    如上面的

    LogGlobalFilter

    示例,通过

    getOrder()

    方法返回 order 值;

  • 方式 2:使用 @Order 注解

    :java运行

    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    @Component
    @Order(8) // order 值为8,优先级高于上面的 LogGlobalFilter(order=5?不,8>5,优先级更低)
    public class AuthGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    System.out.println("全局认证过滤器执行");
    return chain.filter(exchange);
    }
    }

3. 执行顺序示例(直观理解)#

假设存在以下过滤器:

过滤器类型order 值执行阶段
GlobalFilter A5pre
GatewayFilter B10pre
GlobalFilter C15pre
GatewayFilter B10post
GlobalFilter C15post
GlobalFilter A5post

实际执行顺序

  1. pre 阶段:A(5)→ B(10)→ C(15)→ 转发请求到后端服务;
  2. post 阶段:C(15)→ B(10)→ A(5)→ 返回响应给客户端。

三、关键补充:过滤器执行的特殊场景#

  1. 负数 order:允许设置负数(如 order=-1),优先级更高(比 0 先执行),常用于核心全局过滤器(如跨域处理);
  2. 相同 order:同 order 的过滤器执行顺序不保证(尽量避免),可通过微调 order 值(如一个设 5,一个设 6)解决;
  3. 内置过滤器的默认 order:Gateway 内置的 GlobalFilter(如负载均衡、路由转发)有默认 order,比如负载均衡过滤器 LoadBalancerClientFilter 的 order 是 0,路由转发过滤器 RouteToRequestUrlFilter 的 order 是 10000

总结#

  1. Gateway Filter 分为路由级(GatewayFilter)(作用于指定路由)和全局级(GlobalFilter)(作用于所有路由)两类;
  2. 执行顺序核心靠 order 属性控制:数值越小优先级越高,pre 阶段升序执行、post 阶段降序执行;
  3. GatewayFilter 可通过配置 / 代码设置 order,GlobalFilter 推荐实现 Ordered 接口(或用 @Order 注解)指定 order,且 Ordered 接口优先级更高。
Spring Cloud Gateway 实现限流的方式#

Spring Cloud Gateway 官方提供了 RequestRateLimiterGatewayFilterFactory 组件实现限流,核心是结合 Redis令牌桶算法(Token Bucket),支持按 IP、用户 ID、接口路径等多维度限流,具体实现步骤如下:

1. 引入必要依赖#

Gateway 是响应式框架,需引入 Redis 响应式依赖(非普通的 spring-boot-starter-data-redis),同时引入 Gateway 核心依赖:

<!-- Gateway 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Redis 响应式依赖(限流核心,必须用 reactive 版本) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

2. 定义限流维度(KeyResolver)#

KeyResolver 是限流的 “维度定义器”,用于决定按什么维度限流(如 IP、用户、接口),需自定义实现该接口:java运行

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Configuration
public class RateLimitConfig {
// 1. 按客户端IP限流(最常用)
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> {
// 获取客户端IP地址
String clientIp = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return Mono.just(clientIp);
};
}
// 2. 按用户ID限流(需用户登录,从请求头/参数取userId)
/*
@Bean
public KeyResolver userIdKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("X-User-Id")
);
}
*/
// 3. 按接口路径限流
/*
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getPath().toString()
);
}
*/
}

3. 配置 Redis 连接和限流规则#

application.yml 中配置 Redis 地址、令牌桶核心参数(速率、容量):

yaml

spring:
# Redis 连接配置
redis:
host: localhost # Redis 服务地址
port: 6379 # Redis 端口
database: 0 # 数据库索引(默认0)
# Gateway 限流配置
cloud:
gateway:
routes:
- id: user-service-route # 路由ID
uri: lb://user-service # 目标服务(负载均衡)
predicates:
- Path=/user/** # 匹配/user开头的请求
filters:
# 限流过滤器配置
- name: RequestRateLimiter
args:
# 指定限流维度(对应上面定义的 KeyResolver bean 名称)
key-resolver: "#{@ipKeyResolver}"
# 令牌桶每秒填充速率(允许的平均QPS)
redis-rate-limiter.replenishRate: 10
# 令牌桶最大容量(允许的突发QPS)
redis-rate-limiter.burstCapacity: 20
# 每次请求消耗的令牌数(默认1)
redis-rate-limiter.requestedTokens: 1

4. 自定义限流响应(可选)#

默认限流触发时返回 429 Too Many Requests 空响应,可自定义返回 JSON 提示:

java运行

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
@Component
public class CustomRateLimitHandler {
public Mono<Void> handle(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
String msg = "{\"code\":429,\"msg\":\"请求过于频繁,请稍后再试\"}";
DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
}

二、Redis 限流的原理#

Gateway 基于 Redis 的限流核心是 令牌桶算法,并通过 Redis 的 Lua 脚本保证限流逻辑的原子性,避免并发问题,具体原理拆解如下:

1. 令牌桶算法(Token Bucket)基础#

令牌桶是限流的经典算法,核心规则:

  • 系统以固定速率replenishRate)向令牌桶中填充令牌;
  • 令牌桶有最大容量burstCapacity),满了之后不再填充;
  • 每次请求需要从桶中获取指定数量的令牌(requestedTokens);
  • 令牌足够则放行请求(令牌数减少),不足则拒绝请求(限流)。

2. Redis 实现令牌桶的核心逻辑(Lua 脚本)#

Gateway 的 RedisRateLimiter 内置了 Lua 脚本,通过 Redis 存储每个限流维度的令牌状态,保证 “计算令牌数 - 更新令牌数” 的原子性(Redis 单线程执行 Lua 脚本,避免并发计数错误)。

核心 Lua 脚本逻辑(简化版)#
-- 限流维度的key(如 ip:192.168.1.1)
local key = KEYS[1]
-- 参数:replenishRate(每秒填充速率), burstCapacity(最大容量), requestedTokens(单次消耗令牌数), 当前时间
local replenishRate = tonumber(ARGV[1])
local burstCapacity = tonumber(ARGV[2])
local requestedTokens = tonumber(ARGV[3])
local now = tonumber(ARGV[4])
-- 存储令牌状态的两个key
local lastRefreshedKey = key .. ":last_refreshed" -- 最后一次填充令牌的时间
local tokensKey = key .. ":tokens" -- 当前剩余令牌数
-- 获取上次状态(默认:last_refreshed=0,tokens=burstCapacity)
local lastRefreshed = tonumber(redis.call("get", lastRefreshedKey) or 0)
local currentTokens = tonumber(redis.call("get", tokensKey) or burstCapacity)
-- 计算时间差,填充令牌(按速率补充,最多到burstCapacity)
local timeElapsed = now - lastRefreshed
if timeElapsed > 0 then
local newTokens = currentTokens + (timeElapsed * replenishRate)
currentTokens = math.min(newTokens, burstCapacity)
redis.call("set", lastRefreshedKey, now) -- 更新最后填充时间
end
-- 判断是否有足够令牌
local allowed = 0
if currentTokens >= requestedTokens then
allowed = 1
currentTokens = currentTokens - requestedTokens -- 消耗令牌
end
-- 保存更新后的令牌数
redis.call("set", tokensKey, currentTokens)
-- 返回结果:是否允许(1/0)、剩余令牌数、最大令牌数
return {allowed, currentTokens, burstCapacity}
Redis 限流的关键要点#
  1. 原子性保障:Lua 脚本将 “读取状态→计算令牌→更新状态” 封装为原子操作,避免多请求并发时的令牌数错误;

  2. 状态存储

    :每个限流维度在 Redis 中存储两个 key:

    • {key}:last_refreshed:记录最后一次填充令牌的时间戳;
    • {key}:tokens:记录当前剩余令牌数;
  3. 动态填充:每次请求都会计算从上一次请求到现在的时间差,按速率补充令牌,保证令牌数动态更新;

  4. 突发流量处理burstCapacity 允许短时间内的突发流量(比如每秒允许 10 个,最多一次处理 20 个),兼顾限流和灵活性。

总结#

  1. Spring Cloud Gateway 核心通过 RequestRateLimiterGatewayFilterFactory 实现限流,需配置 KeyResolver 定义限流维度、Redis 连接和令牌桶参数(速率 / 容量);
  2. Redis 限流的核心是令牌桶算法,通过 Lua 脚本保证令牌计算 / 更新的原子性,避免并发问题;
  3. Redis 存储限流维度的令牌状态(最后填充时间、剩余令牌数),每次请求动态填充令牌并判断是否放行。
Spring Cloud Gateway 核心组件及作用#

Spring Cloud Gateway 是基于 Spring 5、Spring Boot 2 和 Project Reactor 构建的非阻塞式 API 网关,其核心设计围绕路由 (Route)断言 (Predicate)过滤器 (Filter) 三大组件展开,此外还有一些辅助核心组件(如网关处理器映射器、网关 web 处理器),下面逐一说明:

1. 核心组件一:路由(Route)#

  • 定义:路由是网关最核心的基础单元,是网关的 “转发规则”,由 ID目标URI断言集合过滤器集合 组成。

  • 作用:网关接收到请求后,会根据路由规则匹配请求,匹配成功则将请求转发到指定的目标 URI(后端服务地址)。

  • 核心属性

    • id:路由唯一标识(如 user-service-route),避免重复;
    • uri:请求转发的目标地址(支持 http/httpslb://(负载均衡)、ws/wss 等);
    • predicates:断言集合(判断请求是否匹配当前路由);
    • filters:过滤器集合(对请求 / 响应进行修改);
    • order:路由优先级(数值越小优先级越高,多个路由匹配时生效)。
  • 示例(配置文件方式)yaml

    spring:
    cloud:
    gateway:
    routes:
    - id: user-service-route # 路由ID
    uri: lb://user-service # 转发到用户服务(负载均衡)
    predicates: # 断言:匹配路径以/user/开头的请求
    - Path=/user/**
    filters: # 过滤器:添加请求头
    - AddRequestHeader=X-Request-From, Gateway

2. 核心组件二:断言(Predicate)#

  • 定义:Predicate 是 Java 8 的 java.util.function.Predicate 接口的实现,本质是 “条件判断规则”。

  • 作用:判断请求是否满足当前路由的匹配条件,只有所有断言都返回 true,请求才会被该路由匹配并转发。

  • 常用断言类型

    (Spring Cloud Gateway 内置):

    • Path:匹配请求路径(如 /user/**);
    • Method:匹配请求方法(如 GETPOST);
    • Header:匹配请求头(如 Header=X-Token, \d+ 匹配头中 X-Token 为数字的请求);
    • Query:匹配请求参数(如 Query=name, zhangsan 匹配参数 name=zhangsan 的请求);
    • After/Before/Between:匹配请求时间(如 After=2026-01-01T00:00:00+08:00[Asia/Shanghai] 匹配指定时间后的请求);
    • RemoteAddr:匹配请求客户端 IP(如 RemoteAddr=192.168.1.0/24)。
  • 特点:支持多个断言组合(逻辑与),也可自定义断言满足个性化匹配需求。

3. 核心组件三:过滤器(Filter)#

  • 定义:过滤器是网关对请求 / 响应进行拦截和修改的核心组件,分为 GatewayFilter(路由级别)和 GlobalFilter(全局级别)。

  • 作用:在请求转发到后端服务前 / 后,对请求(如添加头信息、参数、限流)或响应(如修改响应体、添加响应头)进行加工处理。

  • 分类及示例

    类型作用范围常用示例
    GatewayFilter单个 / 部分路由1. AddRequestHeader:添加请求头;2. StripPrefix:去除请求路径前缀(如 /api/user 去除 /api 后转发到 /user);3. Retry:请求失败重试;4. RequestRateLimiter:限流(基于 Redis)。
    GlobalFilter所有路由1. 身份认证(如校验 Token);2. 日志记录;3. 跨域处理;4. 全局限流。
  • 执行顺序

    • 过滤器有 order 属性,数值越小执行优先级越高;
    • 请求转发前(pre)的过滤器按 order 升序执行,请求返回后(post)的过滤器按 order 降序执行。

4. 辅助核心组件#

RouteLocator:路由定位器,负责加载和管理所有路由规则,支持配置文件、代码编码、动态路由(如从 Nacos 拉取)等多种路由加载方式。

GatewayHandlerMapping:网关处理器映射器,接收客户端请求后,通过 RouteLocator 获取所有路由,结合断言匹配出最合适的路由。

GatewayWebHandler:网关 Web 处理器,执行匹配路由的过滤器链(GlobalFilter + GatewayFilter),完成请求转发和响应处理。

核心组件工作流程(补充)#

为了让你更清晰理解组件间的配合,这里梳理核心流程:

  1. 客户端发送请求到 Spring Cloud Gateway;
  2. GatewayHandlerMapping 通过 RouteLocator 获取所有路由,结合 Predicate 匹配请求对应的路由;
  3. 匹配成功后,GatewayWebHandler 执行该路由的 GlobalFilter + GatewayFilter 链(pre 阶段);
  4. 请求转发到后端服务,获取响应;
  5. 执行过滤器链的 post 阶段;
  6. 将响应返回给客户端。

总结#

  1. 路由 (Route) 是网关的核心转发规则,包含 ID、目标 URI、断言和过滤器,是请求转发的 “导航规则”;
  2. 断言 (Predicate) 是路由的 “匹配条件”,只有满足所有断言的请求才会被当前路由处理;
  3. 过滤器 (Filter) 是网关的 “加工工具”,分为路由级别和全局级别,负责请求 / 响应的拦截和修改,是实现限流、认证、日志等功能的核心。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Spring Cloud GateWay
https://www.liuguang.top/posts/microservices/article-20260501-spring-cloud-gateway/
作者
LiuGuang
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
LiuGuang
Hello, I'm LiuGuang.
公告
没啥官方话术,热烈欢迎到访!
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
6
分类
5
标签
8
总字数
946,977
运行时长
0
最后活动
0 天前

目录