开头咱们先说说题目,是不是第二部,套用沈腾的一句经典台词:都这时候了,你还较那三部二部的真,有意义吗?😂😂🤣 从某个方面这题目也还行,就暂且这样吧。
- 网关在架构中扮演着"流量枢纽"与"治理中枢"的角色,其核心本质是反向代理层 与协议转换器
- 也可以说网关本质上是在协调"客户端到网关"和"网关到下游"两段连接
一、 深层次原理:网关究竟在做什么?
在 OSI 七层模型中,传统的路由器(Router)只工作在 L3(网络层) ,它只关心 IP 地址,只要 IP 对了,就直接把数据包扔过去。而网关(Gateway)工作在 L7(应用层),它不仅要看 IP,还要"拆开信封看里面的信",然后再"换个信封重新寄出去"。
[客户端]
│
│ 1. 发送 HTTP 请求 (TCP/IP 数据包)
▼
[网关 Gateway] (L7 应用层)
├─ 2. 拆包 (Unpack):剥离 TCP/IP 头部,提取 HTTP Payload (如 JSON)
├─ 3. 策略引擎 (Policy Engine):
│ ├─ 鉴权:解析 JSON 中的 JWT Token 是否合法
│ ├─ 限流:基于 Redis 令牌桶判断是否放行
│ └─ 路由:根据 URL 决定转发给哪个后端服务
├─ 4. 状态转换与重写 (Rewrite):
│ ├─ 添加 Header:塞入 `X-User-ID: 10086`
│ └─ 路径重写:`/api/v1/user` 改为 `/user`
└─ 5. 重新打包与转发 (Pack & Forward):
├─ 建立与后端服务的 TCP 连接(复用连接池)
└─ 发送新的 HTTP 请求
▼
[后端微服务]
- 全层感知与协议解析:网关接收数据包后,不仅剥离网络层头部,还会深入应用层(如 HTTP、gRPC、MQTT)解析 Payload。
- 策略引擎(Policy Engine):基于解析出的内容,执行鉴权(JWT 校验)、限流(令牌桶/漏桶算法)、熔断降级等逻辑。
- 状态转换与重写:修改请求头(Header)、路径重写(Path Rewriting),甚至进行跨协议转换(如 HTTP 转 gRPC)。
- 高性能转发与连接池:将处理后的请求通过内部连接池路由至后端微服务,并处理响应缓冲与编码解码。
二、 源码级剖析:以 Spring Cloud Gateway 为例
在主流 Java 微服务架构中,Spring Cloud Gateway 是典型代表。它的核心机制依赖于 WebFlux + Netty 的非阻塞 I/O 模型。
1. 核心组件架构:
-
DispatcherHandler请求分发器:网关的核心入口类似于 Spring MVC 的DispatcherServlet,所有 HTTP 请求进入网关后的第一站,负责将请求分发给具体的MappingHandlerMapping。
*public Mono<Void> handle(ServerWebExchange exchange) { // 1. 遍历所有的 HandlerMapping,寻找能处理当前请求的 Handler return Flux.fromIterable(this.handlerMappings) .concatMap(mapping -> mapping.getHandler(exchange)) // 遍历mapping调用getHandler异步获取 .next() // 只要找到第一个匹配的 Handler 就停止遍历 .switchIfEmpty(createNotFoundError()) // 找不到则返回 404 // 2. 找到后,异步调用对应的 Handler 处理业务逻辑 .flatMap(handler -> invokeHandler(exchange, handler)) // 3. 异步,处理 Handler 返回的结果 .flatMap(result -> handleResult(exchange, result)); }- Flux:异步响应流、异步序列完成时发出完成/错误信号;与list不同他是惰性且非阻塞的
- 数据可以源源不断的来,订阅者处理完一个再要下一个
- 并且根据上下游自动调整数据流的传输速率(借助队列而不是阻塞)(这就是大佬们说的backpressure背压机制了)
- Flux:异步响应流、异步序列完成时发出完成/错误信号;与list不同他是惰性且非阻塞的
-
RouteLocator:路由定位器,负责根据配置(各种数据源如 YAML 配置文件、Nacos 动态配置)获取路由定义,解析出路由规则(Predicates 和 Filters)转换为可执行route对象
*protected Mono<Route> lookupRoute(ServerWebExchange exchange) { return this.routeLocator.getRoutes() // 1. 获取所有路由(返回响应式流 Flux<Route>) .concatMap(route -> Mono.just(route) // 2. 核心:使用 AsyncPredicate 异步断言进行匹配 //简单来说:咱们配置文件断言指定了请求的url、method、header,命中则匹配上 .filterWhen(r -> r.getPredicate().apply(exchange)) ) .next() // 3. 取第一个匹配成功的路由 .map(route -> { validateRoute(route, exchange); // 校验路由合法性 return route; }); }- Flux<Route>包含多个路由对象的异步数据流:系统动态加载(大量)路由,请求发起,框架逐步匹配,当匹配上某条件后由下游handler处理。
SO RouteLocator支持动态响应式流(Flux): 当在 Nacos 中修改了路由配置,RouteLocator会感知到变化并刷新内存中的路由表。匹配过程使用的是AsyncPredicate,不会阻塞 Netty 的 I/O 线程。
-
FilteringWebHandler:核心处理器,负责构建过滤器链(Filter Chain)。- 当
DispatcherHandler拿到了FilteringWebHandler后,真正的业务逻辑(鉴权、限流、路由转发)才开始执行。FilteringWebHandler的核心职责是合并过滤器并构建责任链。
public Mono<Void> handle(ServerWebExchange exchange) { // 1. 从 Exchange 中获取刚才匹配到的路由信息 Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); // 2. 获取该路由下配置的局部过滤器 List<GatewayFilter> gatewayFilters = route.getFilters(); // 3. 【核心】合并全局过滤器(GlobalFilter)和局部过滤器 List<GatewayFilter> combined = new ArrayList<>(this.globalFilters); combined.addAll(gatewayFilters); // 4. 按照 @Order 注解或 Ordered 接口进行排序 AnnotationAwareOrderComparator.sort(combined); // 5. 创建过滤器链并触发执行:依次调用每个 Filter 的 filter(exchange, chain) return new DefaultGatewayFilterChain(combined).filter(exchange); }-
NettyRoutingFilter是背后真正的大佬:网关执行请求转发的核心组件:Reactor Netty 的非阻塞 I/O 模型
*public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 获取目标 URI URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); // 2. 获取 Reactor Netty 的 HttpClient HttpClient httpClient = getHttpClient(); // 3. 异步发起下游请求 Mono<HttpClientResponse> responseMono = httpClient .request(HttpMethod.valueOf(exchange.getRequest().getMethodValue())) .uri(requestUrl) .send((req, nettyOutbound) -> nettyOutbound.send(exchange.getRequest().getBody())) .response(); // 4. 响应返回后,继续执行后置过滤器链 return responseMono.then(chain.filter(exchange)); }内部使用 Reactor Netty 提供的
ConnectionProvider(连接池),所以网关与下游微服务之间维持长连接(Keep-Alive),避免了高并发下频繁进行 TCP 三次握手和四次挥手的巨大开销。 -
如果链中某个filter抛出异常,mono.defer捕获异常将其转成Mono.error(ex)传播,中断后续过滤器的执行
/** * 全局兜底异常处理 * filter异常无法被@controllerAdvice捕获 * 所以提供了WebExceptionHandler机制全局兜底 */ @Component @Order(-1) // 高优先级异常处理器 public class GatewayExceptionHandler implements WebExceptionHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { // 1. 记录异常日志 logger.error("Gateway exception occurred", ex); // 2. 根据异常类型映射 HTTP 状态码 if (ex instanceof UnauthorizedException) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); } else { exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); } // 3. 设置响应完成,返回给客户端 return exchange.getResponse().setComplete(); } } - 当
所以一次请求的底层串联我们就可以大致看到:
- 客户端发起请求,Netty 的 Worker 线程接收数据。
- 数据交给
DispatcherHandler,它遍历寻找匹配的 Handler。 RoutePredicateHandlerMapping调用RouteLocator,通过异步断言找到匹配的路由,并返回FilteringWebHandler。DispatcherHandler调用FilteringWebHandler的 handle 方法。FilteringWebHandler组装全局和局部过滤器,构建DefaultGatewayFilterChain,依次执行鉴权、限流等逻辑,最后由NettyRoutingFilter将请求转发给下游微服务。
2. 过滤器链(Filter Chain)执行原理:
网关的核心扩展能力来自过滤器。源码中通过 GlobalFilter(全局)和 GatewayFilter(局部)实现。
@Component
public class ArchitectureGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 【L7 内容感知 1】:提取应用层数据(如 Header)
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 【策略引擎】:鉴权逻辑
if (StringUtils.isBlank(token) || !JwtUtil.isValid(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); // 直接拦截,不再往下走
}
// 【L7 内容感知 2】:状态转换与重写
// 1. 路径重写
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
.path("/user-service" + exchange.getRequest().getPath())
// 2. Header 重写:把解析出的 UserID 塞进 Header,透传给下游
.header("X-User-ID", JwtUtil.getUserId(token))
.build();
// 将修改后的请求传递给下一个过滤器或后端服务
return chain.filter(exchange.mutate().request(mutatedRequest).build());
}
@Override
public int getOrder() {
return -100; // 优先级最高
}
}
- 网关不能像传统 Tomcat 那样"一个请求一个线程",否则高并发下线程会瞬间耗尽。
- SCG 底层使用
NettyRoutingFilter进行转发。它通过 Netty 的 Reactor 线程模型 ,使用极少的线程处理成千上万的连接。- 事件驱动与非阻塞
- BossGroup(主线程组):只负责一件事------监听和接收客户端的 TCP 连接(Accept 事件),接收完后把连接丢给 WorkerGroup
- WorkerGroup(工作线程组):负责处理连接上的 I/O 读写事件(Read/Write)。
- 业务处理:Worker 线程通过 Selector 监听事件,一旦有数据可读,就分发给对应的 Handler 处理。如业务逻辑耗时会丢给独立的业务线程池,处理完再交回 Worker 线程发送响应。
- 同时,网关内部维护了一个连接池,与后端服务保持长连接(Keep-Alive),避免了每次转发都进行 TCP 三次握手的巨大开销。就这样双管齐下~
3. 高性能限流原理:
网关通常结合 Redis 实现分布式限流。底层采用 令牌桶算法(Token Bucket) 。例如配置 replenishRate: 10(每秒生成10个令牌),burstCapacity: 20(最大突发20)。网关在转发前通过 Lua 脚本原子性地扣减 Redis 中的令牌,若失败则直接返回 429 状态码,避免后端被打垮。这个咱们刚写了令牌和漏斗,这就不多说了;
三、 同类网关对比与选型
| 维度 | Spring Cloud Gateway | Kong | Nginx / OpenResty |
|---|---|---|---|
| 底层语言 | Java (WebFlux/Netty) | Go / Lua | C / Lua |
| 生态契合度 | Spring Cloud / Alibaba 生态无缝集成 | 云原生、多语言、跨平台 | 传统微服务、静态资源代理 |
| 性能表现 | 优秀(单机数万QPS) | 极高(超高性能,支持多语言插件) | 极致性能(百万级并发) |
| 动态能力 | 支持配置中心(如Nacos)热更新 | 强大的 Admin API,动态路由极强 | 需借助 OpenResty 或外部模块 |
| 适用场景 | Java 微服务生态,需要复杂业务逻辑(如动态鉴权、灰度) | 超大规模集群、跨语言微服务、K8s 环境,需要丰富的插件生态 | 边缘计算、极致性能要求、极高并发场景、静态资源、边缘节点、传统架构 |
四、 落地:脚着地啦
- 统一安全与鉴权:将 JWT 校验、OAuth2、IP 黑名单等逻辑全部收敛在网关层。后端微服务只需关注业务,无需重复造轮子。
- 灰度发布与 A/B 测试 :通过网关解析请求头(如
X-User-Tag: beta),将特定流量路由到新版本的服务集群,实现无感发布。 - 边缘计算与协议转换:在工业物联网场景中,边缘网关负责将 Modbus、OPC UA 等异构协议转换为 MQTT/HTTP,并在本地进行数据脱敏与 AI 推理,降低云端带宽消耗。
- 统一异常与日志监控:在网关层统一拦截异常,返回标准化的 JSON 错误体;同时记录全链路 TraceID,对接 Prometheus+Grafana 实现可观测性。
实例
1、动态路由配置:看着简单底层做了一些事情,上面咱们也讲到了(配合注册中心如nacos)
spring:
cloud:
gateway:
routes:
- id: order-service # 订单服务路由
uri: lb://order-service # lb:// 前缀表示开启负载均衡
predicates:#这就是咱们上面说的断言,RouteLocator
- Path=/api/order/** # 匹配 /api/order/ 开头的请求
filters:
- StripPrefix=1 # 转发时去掉第一层路径 (/api/order/create -> /order/create)
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1000 # 每秒生成*个令牌,写到配置中心
redis-rate-limiter.burstCapacity: 2000 # 最大允许突发*个请求,写到配置中心
key-resolver: "#{@userIdKeyResolver}" # 按用户ID或IP进行限流
-
为防止恶意爬虫或 DDoS 攻击打垮后端,网关通常会结合 Redis 实现基于"令牌桶算法"的分布式限流。老朋友了
*#以按 IP 限流为例: @Bean public KeyResolver ipKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getRemoteAddress().getHostString() ); } #以按用户 ID 限流为例,自定义也可以结合起来,自定义嘛 @Bean public KeyResolver userKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getQueryParams().getFirst("userId") ); } -
当某个用户或 IP 的请求频率超过设定的阈值时,网关会直接返回
429 Too Many Requests状态码,从而保护下游的核心业务服务不被流量洪峰压垮。
2、自定义的全局过滤器,会被**FilteringWebHandler** 处理,加载到链中,很标准的过滤器
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
// 1. 放行公开接口(如登录、健康检查)
if (path.startsWith("/api/public/") || path.equals("/actuator/health")) {
return chain.filter(exchange);
}
// 2. 拦截受保护接口,校验 Token
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token == null || !isValidToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); // 鉴权失败,直接返回 401
}
// 3. 鉴权通过,继续往下走
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -100; // 设置高优先级,确保鉴权最先执行
}
}
五、 "避坑指南"(反例子)
当前离开业务一切都是空谈,如果你量到位那各种策略也是要到位的,如果咱没什么并发也不需要整这么费劲,业务与数据为王。
- ❌ 在网关中编写复杂业务逻辑 :咱就是薄薄的一层
- 反面案例 :在网关的 Filter 中写数据库查询、复杂的订单组装逻辑。这会导致网关吞吐量断崖式下跌,原本单机几万 QPS 的网关瞬间被打满。
- 网关基于 WebFlux 响应式非阻塞模型,核心优势是高并发下的低资源消耗。如在 Filter 中执行同步的数据库查询、RPC 调用或复杂的 JSON 解析,会直接阻塞 Reactor 的事件循环线程(EventLoop)。
- 正解 :网关是"守门人"不是"业务处理机"。它只负责路由、代理、鉴权和限流(如 JWT 验签、路径重写、IP 黑白名单)。业务逻辑必须下沉到具体的微服务中。
- 如果必须在网关层做少量复杂逻辑(如动态查库获取白名单),必须使用响应式客户端(如
Redisson),并通过.subscribeOn(Schedulers.boundedElastic())将阻塞操作隔离到独立线程池,绝不能污染 Netty 的 IO 线程。
- 如果必须在网关层做少量复杂逻辑(如动态查库获取白名单),必须使用响应式客户端(如
- 反面案例 :在网关的 Filter 中写数据库查询、复杂的订单组装逻辑。这会导致网关吞吐量断崖式下跌,原本单机几万 QPS 的网关瞬间被打满。
- ❌ 忽略熔断与超时配置
- 反面案例 :后端服务响应慢,网关一直傻等,网关的 HTTP 客户端连接池(默认 500)会被迅速耗尽,后续所有请求都会卡在
pendingAcquireTimeout上排队,导致整个网关瘫痪,引发全局雪崩。 - 正解: 必须在网关层配置全局或路由级的
response-timeout(如 3s),绝不能无限等待。必须配置熔断降低策略:集成 Resilience4j 或 Sentinel ,配置基于慢调用比例 (如 P99 > 2s 且占比超 50%)或异常比例 (如 5xx 超 30%)的熔断规则。必须针对不同核心域的服务(如订单、支付)配置独立的线程池或信号量,防止某个非核心服务拖垮全局连接池。
- 反面案例 :后端服务响应慢,网关一直傻等,网关的 HTTP 客户端连接池(默认 500)会被迅速耗尽,后续所有请求都会卡在
- ❌ 路由规则划分不合理
- 反面案例:一个网关实例代理了上百个微服务,单点故障风险极高。
- 正解 :按业务域(BFF 模式)拆分网关。例如分为"用户网关"、"交易网关",避免单网关成为性能瓶颈。核心网关与非核心网关必须独立部署,甚至使用不同的集群,做到真正的故障域隔离。因材施教、多少年的经典
- ❌ 忽视安全防护
- 反面案例:网关未配置 CORS(跨域),或未对敏感接口做防重放、防注入处理,导致被恶意刷接口。或者未限制请求体大小,被攻击者利用超大 Payload 耗尽网关内存。
- 正解 :在网关层统一配置安全防护策略,拦截非法请求。
- 如果网关已配置
globalcors,后端微服务必须关闭 自身的跨域配置,否则浏览器会因收到两个Access-Control-Allow-Origin头而直接拦截请求。 - 必须配置
spring.cloud.gateway.httpclient.max-request-size(如 50MB),从网络层直接拒绝超大请求,防止恶意 POST 请求打满网关内存。 - 在全局 Filter 中统一移除或覆盖敏感 Header(如
X-Forwarded-For、内部服务间调用的Auth-Token),防止客户端伪造内部调用凭证。
- 如果网关已配置
总结:
网关的设计哲学是**"极简与防御"**。它不是万能的,任何超出"路由、鉴权、限流、熔断"范畴的逻辑,都是对网关高并发特性的破坏。在评审网关代码时,我们要像审查"核心中间件"一样严苛,关注其底层的线程模型、连接池状态和故障隔离能力。