零拷贝转发本质是请求体从网络缓冲区直通下游,不落地、不解析、不序列化;需用directbytebuf、禁用自动解析、复用内存池并全程监控链路完整性。

在反应式网关(如 Spring Cloud Gateway 基于 Netty)中,平滑处理大数据包的关键不是“避免读取”,而是“不把数据拖进 JVM 堆里做无谓搬运”。零拷贝转发的本质是让请求体从网络接收缓冲区直接流向下游连接,中间不落地、不解析、不序列化——这只有依托堆外直接内存与 Netty 的原生能力才能稳定达成。
用 DirectByteBuf 接收并透传,跳过堆内存中转
默认情况下,Netty 的 HttpRequestDecoder 可能将请求体解码为 HeapByteBuf(尤其当未显式配置时),这就立刻破坏了零拷贝路径。必须确保:
- 全局配置 Netty 的
PooledByteBufAllocator,启用堆外内存池:new PooledByteBufAllocator(true) - 在自定义
WebFilter或GlobalFilter中,检查入参ServerWebExchange的getRequestBody()是否为DirectByteBuf;若为HeapByteBuf,说明已被提前读取或复制,需回退到非零拷贝路径 - 转发前调用
buffer.retain()防止被上游释放,再通过NettyDataBufferFactory构造下游可写缓冲区
禁用自动 body 解析,绕过 Spring WebFlux 的中间层劫持
Spring WebFlux 默认会尝试将请求体转为 Mono<databuffer></databuffer> 并缓存,这对小请求友好,但对大文件就是性能杀手。应主动关闭:
- 在
application.yml中设置:spring.codec.max-in-memory-size: -1(禁用内存缓存) - 在过滤器中使用
exchange.getRequest().getBody()直接获取原始Flux<databuffer></databuffer>,不调用exchange.getFormData()或exchange.getRequestBody()等触发解析的方法 - 若需校验但又不想全量加载,可基于
CompositeByteBuf做流式切片校验(例如只读前 4KB 检查 JSON 结构),保持后续部分仍为原始引用
下游转发时复用同一块物理内存视图
真正实现“零拷贝转发”,不只是上游不拷贝,下游也得接得住:
- 构造下游
HttpClient时,指定使用相同ByteBufAllocator实例,确保上下游 buffer 共享同一内存池 - 用
NettyClientHttpConnector替换默认 connector,并在doConnect阶段注入自定义ChannelOption,如ALLOCATOR和WRITE_BUFFER_WATER_MARK - 转发时调用
outbound.sendObject(buffer)而非sendString()或sendByteArray(),避免隐式转换 - 对超大请求(如 >100MB),可配合
FileRegion+sendfile(仅限文件上传后落盘场景),但注意:HTTP 请求体本身无法直接用FileRegion,需先写入临时文件再触发
监控与兜底:识别何时零拷贝已失效
零拷贝不是开关,而是一条脆弱的链路。一旦某个环节发生隐式拷贝,整条链就退化。建议加入轻量级检测:
- 在关键过滤器中打印
buffer.isDirect()和buffer.hasArray(),若后者为true,说明已是堆内数组 - 用 JVM 参数
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps观察大对象是否频繁触发老年代 GC —— 这往往是堆内 buffer 泛滥的信号 - 对非零拷贝路径,启用异步缓冲降级:将 body 写入
ByteBuffer池或临时文件,再走常规转发,避免 OOM











