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 若仍持续向该服务转发请求,会导致:
- 网关线程池被大量阻塞请求占满,无法处理其他正常服务的请求;
- 故障服务的请求积压,进一步加剧服务不可用;
- 故障扩散到上下游服务,最终引发整个微服务集群雪崩。
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 控制台
- 下载 Sentinel 控制台 jar 包:Sentinel 官方下载;
- 启动控制台:
java -jar sentinel-dashboard-1.8.7.jar --server.port=8080- 访问控制台:
http://localhost:8080(默认账号 / 密码:sentinel/sentinel)。
步骤 4:配置限流规则(前置防护)
通过 Sentinel 控制台配置网关限流规则,控制单接口 / 单 IP 的请求量:
-
进入控制台 → 网关流控 → 新增网关流控规则:
配置项 示例值 说明 资源名 user-service-route 对应 Gateway 的路由 ID(或接口路径:/user/**) 限流类型 QPS 按每秒请求数限流(也可选 “并发数”) 阈值 100 每秒最多允许 100 个请求 流控模式 直接 直接限流当前资源 流控效果 快速失败 超出阈值直接返回降级响应(也可选 “Warm Up” 预热、“匀速排队”) 限流粒度 IP 按客户端 IP 限流(也可选手动选择、参数等)
步骤 5:配置熔断降级规则(故障隔离)
当后端服务异常率 / 超时率达到阈值时,自动熔断该服务,避免持续转发请求:
-
进入控制台 → 网关熔断 → 新增网关熔断规则:
配置项 示例值 说明 资源名 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降级响应处理器 */@Componentpublic 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;
@RestControllerpublic class FallbackController { @RequestMapping("/fallback/user") public Mono<String> userFallback() { return Mono.just("{\"code\":503,\"msg\":\"用户服务暂不可用\",\"success\":false}"); }}四、关键优化点(避免雪崩的核心细节)
- 设置合理的超时时间:Gateway 转发请求时设置超时(如 1 秒),避免线程长时间阻塞:
spring: cloud: gateway: httpclient: connect-timeout: 500 # 连接超时500ms response-timeout: 1000 # 响应超时1秒- 降级策略差异化:核心接口(如支付)返回 “排队提示”,非核心接口(如商品列表)返回 “缓存数据”,提升用户体验;
- 监控告警:通过 Sentinel 控制台 / Prometheus 监控限流 / 熔断指标,异常时及时告警(如熔断触发、QPS 突增);
- 限流粒度精细化:对热点接口(如秒杀)按 “用户 ID + 商品 ID” 限流,避免单一用户刷爆接口;
- 预热限流:对新上线服务使用 “Warm Up” 流控效果,避免刚启动就承受满流量。
总结
-
Gateway 避免服务雪崩的核心是
限流(前置控流量)+ 熔断降级(故障隔离)
双管齐下:
- 限流:控制进入系统的总流量,避免后端服务过载;
- 熔断降级:后端服务故障时切断请求,防止故障扩散;
-
优先选择 Sentinel 实现一体化防护(限流 + 熔断 + 监控),轻量场景可选用 Resilience4j + Redis 限流;
-
关键细节:合理设置超时时间、降级策略、监控告警,确保防护规则适配业务场景。
通过这套方案,能有效隔离故障服务、控制流量峰值,从入口层杜绝服务雪崩的发生。
先理清两种限流在 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 集群,不想引入新组件 | 追求低性能开销、可视化运维、全栈流量治理 |
| 网关流量适中,对网络开销不敏感 | 高并发场景(如秒杀),要求本地限流、低延迟 |
| 仅需 “限流” 单一功能,无熔断需求 | 微服务架构,需保护后端服务(熔断降级) |
总结
- Redis 限流是轻量、单一功能的方案,依赖 Redis,适合简单限流场景,已有 Redis 集群时接入成本低;
- Sentinel 是功能全面、性能更优的流量治理方案,无强制外部依赖,支持限流、熔断、监控一体化,适合复杂微服务场景;
- 核心决策点:是否需要熔断 / 降级 / 精细化限流 / 可视化监控 —— 需要则选 Sentinel,仅基础限流则选 Redis。
Gateway 权限校验的核心原理
Gateway 本身不提供内置的权限校验功能,但可通过 GlobalFilter(全局过滤器) 实现全量请求拦截,在请求转发到后端服务的 pre 阶段完成权限校验,核心流程:
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[放行请求到后端服务]二、实操实现:权限校验完整方案
以下是最常用的 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 */@Componentpublic 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;
/** * 权限白名单配置:登录、注册、健康检查等接口无需校验 */@Configurationpublic 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校验 + 接口权限校验 */@Componentpublic 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/**三、进阶优化(可选)
- Token 黑名单:若用户登出 / Token 被盗,需将 Token 加入 Redis 黑名单,在
validateToken中增加黑名单校验; - 分布式权限缓存:将用户权限列表缓存到 Redis,避免每次解析 Token(适合权限频繁变更的场景);
- 细粒度角色校验:在过滤器中增加角色校验(如
admin角色可访问所有接口); - 限流 + 权限联动:权限校验通过后,再执行限流逻辑(调整过滤器 order 值,权限过滤器 order 更小)。
总结
- Gateway 权限校验核心是GlobalFilter,在
pre阶段拦截请求,优先校验白名单,再做 Token 身份认证和接口权限校验; - 基础校验需实现 JWT 工具类(Token 生成 / 解析)+ 白名单配置 + 全局过滤器;
- 校验失败需返回标准化 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. 负载均衡核心流程
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透传给客户端]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;
/** * 负载均衡策略配置类 */@Configurationpublic 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:
- 进入 Nacos 控制台 → 服务管理 → 服务列表 → 点击
user-service; - 编辑实例的元数据,添加
weight: 10、weight: 20、weight: 30(权重越高,被选中概率越大)。
步骤 5:测试自定义策略
- 启动网关和多个
user-service实例(配置不同权重); - 多次访问
http://网关IP:网关端口/user/xxx; - 查看后端实例的日志,验证权重高的实例接收的请求数更多(如权重 30 的实例接收请求数约为权重 10 的 3 倍)。
三、其他自定义策略场景
| 策略类型 | 适用场景 | 核心实现思路 |
|---|---|---|
| 最少连接数策略 | 后端实例性能差异大 | 记录每个实例的当前连接数,选择连接数最少的实例 |
| IP 哈希策略 | 保证同一客户端请求到同一实例 | 对客户端 IP 做哈希运算,映射到固定实例(解决会话粘滞问题) |
| 区域优先策略 | 多机房部署 | 优先选择与网关同区域的实例,跨区域实例作为兜底 |
总结
- Gateway 负载均衡依赖 Spring Cloud LoadBalancer,通过
lb://前缀触发,核心流程是「获取实例列表 → 按策略选实例 → 替换 uri 转发」; - 自定义负载均衡策略需实现
ReactorServiceInstanceLoadBalancer接口,适配 Gateway 的响应式架构; - 权重策略是最常用的自定义场景,需在后端实例的 metadata 中配置权重,在策略类中按权重区间选择实例。
关键点:自定义策略需适配响应式模型(使用 Mono/Flux),避免阻塞操作,确保 Gateway 的高性能。
动态路由的核心原理
Gateway 的路由规则默认从本地配置文件(application.yml)加载(静态路由),由 PropertiesRouteDefinitionRepository 管理,修改后需重启网关生效。
动态路由的核心思路:
- 替换默认的
RouteDefinitionRepository接口实现(该接口负责路由规则的增、删、改、查); - 将路由规则存储到外部配置源(Nacos/Redis/MySQL 等);
- 监听外部配置源的变更,触发 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:88483. 自定义动态路由仓库(核心)
实现 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;
@Componentpublic 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. 测试动态路由
- 启动网关服务,验证路由规则是否从 Nacos 加载;
- 在 Nacos 控制台修改路由规则(比如新增 / 删除路由、修改 Predicate/Filter);
- 无需重启网关,直接访问测试,验证路由规则已实时生效。
三、其他动态路由实现方式
除了 Nacos,还可基于 Redis/MySQL 实现,核心思路一致(替换路由仓库 + 监听配置变更):
1. 基于 Redis 实现
- 路由规则以 JSON 格式存储在 Redis 的 Hash/List 中;
- 自定义
RouteDefinitionRepository,从 Redis 读取路由规则; - 监听 Redis Key 过期 / 变更事件(或通过消息通知),触发路由刷新。
2. 基于 MySQL 实现
- 建表存储路由规则(字段:id、uri、predicates、filters、order 等);
- 自定义
RouteDefinitionRepository,从数据库读取路由规则; - 通过定时任务轮询数据库(简单)或基于 Binlog 监听表变更(实时),触发路由刷新。
四、总结
- Gateway 动态路由的核心是替换
RouteDefinitionRepository,将路由源从本地配置改为外部存储(Nacos/Redis/MySQL); - Nacos 是 Spring Cloud 生态首选,配置简单且支持配置自动推送,无需额外实现监听逻辑;
- 路由变更后需发布
RefreshRoutesEvent事件,让 Gateway 实时感知并重新加载路由规则; - 按需实现
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` | 匹配请求头参数 | ```yamlpredicates: - Header=X-Token, \d+``` | 匹配请求头中包含 `X-Token` 且值为数字的请求;第二个参数支持正则表达式 || `Query` | 匹配 URL 请求参数 | ```yamlpredicates: - Query=userId, 100[1-9] - Query=token # 仅判断参数存在,不校验值``` | 第一个示例:匹配包含 `userId` 参数且值为 1001-1009 的请求;第二个示例:仅判断 `token` 参数存在即可 || `RemoteAddr` | 匹配客户端 IP 地址 | ```yamlpredicates: - RemoteAddr=192.168.1.0/24,10.0.0.0/8``` | 匹配来自 192.168.1.x 网段或 10.x.x.x 网段的客户端请求;支持 CIDR 格式 || `After` | 匹配指定时间**之后**的请求 | ```yamlpredicates: - After=2026-01-01T00:00:00+08:00[Asia/Shanghai]``` | 仅匹配 2026年1月1日 0点之后的请求;时间格式为 `ISO-8601`,需指定时区 || `Before` | 匹配指定时间**之前**的请求 | ```yamlpredicates: - Before=2026-12-31T23:59:59+08:00[Asia/Shanghai]``` | 仅匹配 2026年12月31日 23:59:59 之前的请求 || `Between` | 匹配指定时间区间内的请求 | ```yamlpredicates: - Between=2026-01-01T00:00:00+08:00[Asia/Shanghai],2026-12-31T23:59:59+08:00[Asia/Shanghai]``` | 仅匹配 2026 全年的请求 || `Cookie` | 匹配客户端 Cookie | ```yamlpredicates: - Cookie=username, zhangsan``` | 匹配客户端 Cookie 中包含 `username` 且值为 `zhangsan` 的请求;支持正则 |
#### 补充:多 Predicate 组合使用(逻辑与)实际场景中,常组合多个 Predicate 实现精准匹配,比如:```yamlspring: 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 网段” 的请求,才会被该路由处理。
三、总结
- Gateway 的 Predicate 是路由的匹配条件,基于 Java 8 Predicate 接口,只有满足所有 Predicate 的请求才会被对应路由转发;
- 常用 Predicate 覆盖路径(Path)、方法(Method)、参数(Query)、IP(RemoteAddr)、时间(After/Before) 等核心维度,是配置路由规则的基础;
- 多 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: 102. 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;
// 全局过滤器:记录所有请求的路径(作用于所有路由)@Componentpublic 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;@Componentpublic class CustomGatewayFilterFactory implements GatewayFilterFactory<Object>, Ordered {@Overridepublic GatewayFilter apply(Object config) {return (exchange, chain) -> {System.out.println("路由级过滤器执行");return chain.filter(exchange);};}// 设置 order@Overridepublic 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 {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {System.out.println("全局认证过滤器执行");return chain.filter(exchange);}}
3. 执行顺序示例(直观理解)
假设存在以下过滤器:
| 过滤器类型 | order 值 | 执行阶段 |
|---|---|---|
| GlobalFilter A | 5 | pre |
| GatewayFilter B | 10 | pre |
| GlobalFilter C | 15 | pre |
| GatewayFilter B | 10 | post |
| GlobalFilter C | 15 | post |
| GlobalFilter A | 5 | post |
实际执行顺序:
- pre 阶段:A(5)→ B(10)→ C(15)→ 转发请求到后端服务;
- post 阶段:C(15)→ B(10)→ A(5)→ 返回响应给客户端。
三、关键补充:过滤器执行的特殊场景
- 负数 order:允许设置负数(如
order=-1),优先级更高(比 0 先执行),常用于核心全局过滤器(如跨域处理); - 相同 order:同 order 的过滤器执行顺序不保证(尽量避免),可通过微调 order 值(如一个设 5,一个设 6)解决;
- 内置过滤器的默认 order:Gateway 内置的 GlobalFilter(如负载均衡、路由转发)有默认 order,比如负载均衡过滤器
LoadBalancerClientFilter的 order 是0,路由转发过滤器RouteToRequestUrlFilter的 order 是10000。
总结
- Gateway Filter 分为路由级(GatewayFilter)(作用于指定路由)和全局级(GlobalFilter)(作用于所有路由)两类;
- 执行顺序核心靠
order属性控制:数值越小优先级越高,pre 阶段升序执行、post 阶段降序执行; - 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;
@Configurationpublic 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: 14. 自定义限流响应(可选)
默认限流触发时返回 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;
@Componentpublic 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])
-- 存储令牌状态的两个keylocal 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 - lastRefreshedif timeElapsed > 0 then local newTokens = currentTokens + (timeElapsed * replenishRate) currentTokens = math.min(newTokens, burstCapacity) redis.call("set", lastRefreshedKey, now) -- 更新最后填充时间end
-- 判断是否有足够令牌local allowed = 0if currentTokens >= requestedTokens then allowed = 1 currentTokens = currentTokens - requestedTokens -- 消耗令牌end
-- 保存更新后的令牌数redis.call("set", tokensKey, currentTokens)
-- 返回结果:是否允许(1/0)、剩余令牌数、最大令牌数return {allowed, currentTokens, burstCapacity}Redis 限流的关键要点
-
原子性保障:Lua 脚本将 “读取状态→计算令牌→更新状态” 封装为原子操作,避免多请求并发时的令牌数错误;
-
状态存储
:每个限流维度在 Redis 中存储两个 key:
{key}:last_refreshed:记录最后一次填充令牌的时间戳;{key}:tokens:记录当前剩余令牌数;
-
动态填充:每次请求都会计算从上一次请求到现在的时间差,按速率补充令牌,保证令牌数动态更新;
-
突发流量处理:
burstCapacity允许短时间内的突发流量(比如每秒允许 10 个,最多一次处理 20 个),兼顾限流和灵活性。
总结
- Spring Cloud Gateway 核心通过
RequestRateLimiterGatewayFilterFactory实现限流,需配置KeyResolver定义限流维度、Redis 连接和令牌桶参数(速率 / 容量); - Redis 限流的核心是令牌桶算法,通过 Lua 脚本保证令牌计算 / 更新的原子性,避免并发问题;
- 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/https、lb://(负载均衡)、ws/wss等);predicates:断言集合(判断请求是否匹配当前路由);filters:过滤器集合(对请求 / 响应进行修改);order:路由优先级(数值越小优先级越高,多个路由匹配时生效)。
-
示例(配置文件方式)yaml
spring:cloud:gateway:routes:- id: user-service-route # 路由IDuri: 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:匹配请求方法(如GET、POST);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),完成请求转发和响应处理。
核心组件工作流程(补充)
为了让你更清晰理解组件间的配合,这里梳理核心流程:
- 客户端发送请求到 Spring Cloud Gateway;
- GatewayHandlerMapping 通过 RouteLocator 获取所有路由,结合 Predicate 匹配请求对应的路由;
- 匹配成功后,GatewayWebHandler 执行该路由的 GlobalFilter + GatewayFilter 链(pre 阶段);
- 请求转发到后端服务,获取响应;
- 执行过滤器链的 post 阶段;
- 将响应返回给客户端。
总结
- 路由 (Route) 是网关的核心转发规则,包含 ID、目标 URI、断言和过滤器,是请求转发的 “导航规则”;
- 断言 (Predicate) 是路由的 “匹配条件”,只有满足所有断言的请求才会被当前路由处理;
- 过滤器 (Filter) 是网关的 “加工工具”,分为路由级别和全局级别,负责请求 / 响应的拦截和修改,是实现限流、认证、日志等功能的核心。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!