Spring Cloud Gateway 源码阅读 —— Netty 通信层
Gateway 的下游转发由 NettyRoutingFilter 完成,底层是 Reactor Netty 的 WebClient。理解这一层:代理转发如何实现、连接池如何管理、响应如何处理。
转发链路全景
客户端请求(Netty 接收)
│
▼
FilteringWebHandler 过滤器链
│
├─ RouteToRequestUrlFilter 把路由 URI + 请求路径拼成转发 URL
├─ LoadBalancerClientFilter lb:// → http://ip:port(选实例)
│
▼
NettyRoutingFilter ← 本层核心
│ WebClient 发起到下游的请求
▼
下游服务 → 响应 → 流式返回客户端NettyRoutingFilter:核心转发
源码
java
// org.springframework.cloud.gateway.filter.NettyRoutingFilter
public class NettyRoutingFilter implements GlobalFilter, Ordered {
private final WebClient httpClient; // 转发用 WebClient(Reactor Netty)
private final HttpClient httpClient; // 底层 Netty HttpClient
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 取转发 URL(前面过滤器已算好)
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
// 2. 构造下游请求
ServerHttpRequest request = exchange.getRequest();
HttpMethod method = request.getMethod();
HttpHeaders headers = request.getHeaders();
Flux<DataBuffer> body = request.getBody();
// 3. 发起转发请求(WebClient)
return httpClient.method(method)
.uri(requestUrl.toString())
.headers(h -> h.addAll(headers))
.body(body, DataBuffer.class)
.exchangeToMono(res -> {
// 4. 处理响应:包装成 ServerHttpResponse
return handleResponse(exchange, res);
});
}
}转发流程拆解
1. 从 exchange 属性取转发 URL
└─ 由 RouteToRequestUrlFilter / LoadBalancerClientFilter 提前写入
2. 复制请求:方法、Headers、Body
3. WebClient 发起到下游的请求(异步非阻塞)
4. 拿到下游响应 → 写入 exchange 的响应
5. 继续过滤器链的响应阶段WebClient 响应处理
handleResponse:响应包装
java
private Mono<Void> handleResponse(ServerWebExchange exchange, ClientResponse clientResponse) {
// 1. 复制状态码与响应头
exchange.getResponse().setStatusCode(clientResponse.statusCode());
exchange.getResponse().getHeaders().putAll(clientResponse.headers().asHttpHeaders());
// 2. 流式传输响应体(下游 body → 客户端)
Flux<DataBuffer> responseBody = clientResponse.bodyToFlux(DataBuffer.class);
return exchange.getResponse().writeWith(responseBody);
}响应处理策略
| 场景 | 处理 |
|---|---|
| 普通响应 | 状态码/头复制 + body 流式透传 |
| 大响应 | 流式(Flux<DataBuffer>)不缓冲全量 |
| 压缩 | 透传 Content-Encoding,不重复解压 |
| 超时/断开 | Mono 异常传播,过滤器链异常处理 |
流式 vs 缓冲
Gateway 默认流式透传:响应体边收边发,不整体缓存,因此内存占用低、支持大文件下载与 SSE。
HttpClient 配置与连接池
HttpClient 构建(自动装配)
java
// GatewayAutoConfiguration 中
@Bean
public HttpClient gatewayHttpClient(HttpClientProperties properties) {
// 从 spring.cloud.gateway.httpclient 配置构建
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout())
.responseTimeout(Duration.ofMillis(properties.getResponseTimeout()))
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(properties.getReadTimeout(), TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(properties.getWriteTimeout(), TimeUnit.MILLISECONDS)));
// 配置线程数
if (properties.getPool().getType() == HttpClientProperties.PoolType.FIXED) {
httpClient = httpClient.runOn(Loops.newDispatcher(properties.getThreads()));
}
return httpClient;
}连接池配置
yaml
spring:
cloud:
gateway:
httpclient:
connect-timeout: 10000 # 连接超时(ms)
response-timeout: 30s # 响应超时
read-timeout: 30s # 读超时
write-timeout: 30s # 写超时
max-header-size: 16384
pool:
type: fixed # fixed / elastic
max-connections: 500 # 最大连接数
acquire-timeout: 45000 # 获取连接超时
max-idle-time: 5s # 连接最大空闲
max-life-time: 60s # 连接最大生命周期连接池类型
| 类型 | 行为 | 适用 |
|---|---|---|
| fixed | 固定最大连接数(max-connections),超出等待 acquire-timeout | 生产推荐,连接受控 |
| elastic | 连接数无上限(按需创建),可能资源耗尽 | 演示/开发 |
连接池关键参数
- max-connections:与下游的总连接上限,需结合下游 QPS × 平均响应时间估算
- acquire-timeout:连接不够时排队等待时长,超时抛异常
- max-idle-time:空闲连接回收,避免大量空闲连接占用
- max-life-time:连接最长存活,主动轮换避免长期连接问题
连接复用与性能
Keep-Alive
Reactor Netty 默认启用连接复用(HTTP/1.1 Keep-Alive),同一下游地址的连接会被连接池复用,避免频繁建连开销。
请求 1 ──▶ 连接池取连接 ──▶ 下游
请求 2 ──▶ 复用同一连接 ──▶ 下游(无握手开销)HTTP/2
yaml
spring:
cloud:
gateway:
httpclient:
h2c: true # 启用 h2c(明文 HTTP/2),下游需支持HTTP/2 多路复用进一步降低连接数。
响应缓存与 ModifyResponseBody
CacheRequestBodyFilter
java
// 请求体重放(body 被消费后重新放入 exchange)
public class CacheRequestBodyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 缓存请求体到属性,供后续过滤器重复读取
return DataBufferUtils.join(exchange.getRequest().getBody())
.map(dataBuffer -> {
exchange.getAttributes().put(CACHED_REQUEST_BODY_ATTR, dataBuffer);
return dataBuffer;
})
.then(chain.filter(exchange));
}
}ModifyResponseBody:改写响应体
yaml
- name: ModifyResponseBody
args:
inClass: java.lang.String
outClass: java.lang.Stringjava
@Bean
public ModifyResponseBodyGatewayFilterFactory.Config modifyResponseConfig() {
// 编码器/解码器 bean 可自定义
}java
// 编程式:改写 JSON 响应
@Bean
public RouteLocator customRoute(RouteLocatorBuilder builder) {
return builder.routes()
.route("modify-route", r -> r.path("/api/**")
.filters(f -> f.modifyResponseBody(String.class, String.class,
(exchange, json) -> Mono.just(json.replace("\"status\":\"UP\"", "\"status\":\"OK\""))))
.uri("lb://service"))
.build();
}注意:响应改写会把流式响应缓冲成完整体再修改,大响应慎用。
常见问题
- 连接池耗尽? 调大 max-connections 或缩短下游响应时间;确认 acquire-timeout 是否过小导致排队失败。
- 大响应内存溢出? Gateway 默认流式透传不缓存;只有 ModifyResponseBody/缓存过滤器会整体缓冲。
- 下游慢导致网关超时? 调 response-timeout / read-timeout;区分连接超时与读超时场景。
- 响应乱码/头丢失? 检查自定义过滤器是否覆盖了 Headers,Content-Type 透传是否正确。