Netty 不只是 TCP 框架:它解决的是高并发业务系统的组织问题

如果只把 Netty 当成一个 TCP Server 框架,很容易低估它。
在真实业务系统里,高并发问题往往不是单点技术问题,而是一组链路问题。
比如无人机和机库场景中,后端系统里可能同时存在几类持续流动的链路:
- 机库侧流媒体组件从无人机拉取 RTSP 视频流;
- 机库侧再把视频转成 RTMP 或其他标准流协议推送到云端流媒体服务;
- Web 端通过 WebRTC 播放云端视频;
- 机库 Spring Boot 应用和云端通过 MQTT 消息协作;
- 云端和机库之间还会通过 HTTP / Feign 调用接口。
再往上看,云端入口可能有 HTTP Gateway,云端内部也可能同时存在对外 public MQTT 和对内 private MQTT,机库侧还会有自己的 MQTT Gateway。
这些都可以看成不同层级的系统边界:
- HTTP 请求边界;
- 外部协议适配边界;
- 内部业务协议边界;
- 云边消息桥接边界;
- 视频流协议转换边界。
这些链路表面看起来属于不同技术栈:
RTSP / RTMP / WebRTCMQTTHTTPSpring BootZLMediaKit / FFmpeg / GStreamer / SRS / Janus
但从架构视角看,它们都绕不开同一类问题:
- 大量连接如何被管理?
- 持续事件如何被调度?
- 协议边界如何被拆分?
- 线程为什么不能被慢操作拖住?
- 写出速度跟不上时系统如何自我保护?
这也是学习 Netty 的真正价值。
它不只是让 Java 能写网络程序,而是提供了一套理解高并发业务系统的底层语言:
EventLoopChannelPipelineByteBufCodecOutboundBufferBackpressure
学 Netty,不只是学一个框架,而是学习高并发系统如何组织连接、事件、线程、内存、协议和写出路径。
如果你一路学过:
- 阻塞 IO
- 非阻塞 IO
- IO 多路复用
epoll- 零拷贝
NginxWebFluxSpring Cloud Gateway
最后一定会遇到一个 Java 世界里绕不开的名字:
Netty
很多 Java 高并发网络框架,或者它们的常见运行时,都和 Netty 有关系,比如:
Reactor NettySpring WebFluxSpring Cloud GatewayDubboRocketMQgRPC Java- 部分游戏网关
- IM 长连接服务
- 自定义协议服务
这里先不用纠结每个框架和 Netty 的关系是否完全一样。
比如 Spring WebFlux 本身是响应式 Web 编程模型,不等于 Netty;但它最常见的运行时之一 Reactor Netty,就是站在 Netty 之上的。
所以学 Netty,不只是为了会写一个 TCP Server。
更重要的是理解:
- Java 如何把 NIO、Selector、事件循环、线程模型、内存管理、协议编解码,
- 封装成一个可以支撑高并发网络系统的工程框架。
一句话:
Netty 是 Java 世界对高并发网络编程的一次工程化封装,也是理解很多高并发业务链路的底座模型。
先说明一下本文定位:
- 这是 Netty 专题的开篇文章,重点不是深挖某一段源码,而是建立一张全局认知地图。
- 后续文章会沿 ServerBootstrap、EventLoop、Pipeline、ByteBuf 等源码主线逐篇展开。
如果你是业务架构师候选人,读这一篇时可以先抓一个核心问题:
- 当一个系统同时面对设备接入、消息协作、视频链路、网关转发和慢客户端时,
- 底层到底靠什么模型把这些复杂性组织起来?
一、为什么原生 Java NIO 不够好用?
Java 原生 NIO 已经提供了非阻塞网络编程的基础能力。
核心组件包括:
ChannelSelectorSelectionKeyByteBuffer
用原生 NIO 写服务端,大致流程是:
- 创建 ServerSocketChannel
- 设置 non-blocking
- 绑定端口
- 注册到 Selector
- 循环 selector.select()
- 处理 accept/read/write 事件
- 管理 ByteBuffer
- 处理半包粘包
- 处理连接关闭
- 处理写不完的数据
看起来不复杂,但真正写起来会遇到很多麻烦。
比如:
- Selector 事件怎么分发?
- read 读到半包怎么办?
- write 写不完怎么办?
- 连接断开怎么清理?
- ByteBuffer 怎么复用?
- 业务逻辑不能阻塞 IO 线程,怎么切换线程?
- 协议编解码怎么拆分?
- 异常处理和资源释放怎么统一?
原生 NIO 给的是底层零件,不是完整框架。
这有点像:
- epoll 很强,
- 但你不会直接用 epoll 写完整业务系统。
你需要一个工程化框架,把这些底层能力组织起来。
Netty 做的就是这件事。
二、Netty 到底解决了什么问题?
Netty 解决的不是"Java 没有 NIO"。
Java 有 NIO。
Netty 解决的是:
如何把 NIO 变成稳定、可扩展、可维护的网络框架。
它主要解决了这些问题:
- 线程模型
- 事件循环
- 连接生命周期
- IO 事件分发
- 内存分配与复用
- 协议编解码
- 半包粘包
- 写队列和背压
- 异常传播
- 业务 Handler 扩展
如果用一句话概括:
Netty 把复杂的网络 IO 细节,封装成了 EventLoop + Channel + Pipeline + ByteBuf 这套模型。
这四个概念是理解 Netty 的主干:
EventLoop:
负责事件循环和任务执行。
Channel:
表示一条网络连接或一个 IO 端点。
Pipeline:
负责把 IO 事件传递给一组 Handler。
ByteBuf:
负责高效管理网络数据缓冲区。
后面学 Netty 源码,其实就是围绕这四个问题展开:
- 连接怎么来?
- 事件怎么循环?
- 数据怎么读?
- Handler 怎么处理?
- 数据怎么写出去?
- 内存怎么管理?
先看一个最小化的 Netty 服务端代码:
java
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new EchoServerHandler());
}
});
ChannelFuture future = bootstrap.bind(8080).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
这段代码省略了 import 和 EchoServerHandler 的具体实现,只保留启动主线。
这段代码里其实已经包含了 Netty 的几条核心主线:
ServerBootstrap:
服务端启动入口。
bossGroup / workerGroup:
Netty 的线程模型。
NioServerSocketChannel:
服务端监听 Channel。
ChannelInitializer:
Channel 初始化逻辑。
ChannelPipeline:
IO 事件处理链。
bind(8080):
端口绑定和注册流程的起点。
后面分析 Netty 源码,很多内容就是从这几行代码展开。
三、Netty 和 Nginx 的相似点
前面我们聊过 Nginx。
Nginx 的核心模型是:
- 少量 worker
- 事件驱动
- 非阻塞 IO
- epoll 管理大量连接
- sendfile 优化文件发送
Netty 和 Nginx 在思想上非常相似。
Netty 的核心模型是:
- 少量 EventLoop 线程
- 非阻塞 Channel
- Selector / epoll 管理大量连接
- Pipeline 处理 IO 事件
- ByteBuf 管理网络数据
可以这样类比:
Nginx worker:
一个进程里的事件循环。
Netty EventLoop:
一个线程里的事件循环。
当然,Nginx 和 Netty 的定位不同。
Nginx 是一个成品服务器:
- 配置驱动
- 反向代理
- 静态资源
- 负载均衡
TLS- 限流
Netty 是一个网络编程框架:
- 你可以基于它实现 HTTP Server
- 也可以实现 RPC
- 也可以实现 IM
- 也可以实现网关
- 也可以实现自定义 TCP 协议
Nginx 更像一台现成机器。
Netty 更像一套高性能网络引擎。
四、Netty 的线程模型:不是一个连接一个线程
阻塞 IO 的典型问题是:
连接数和线程数容易绑定。
比如:
1 万连接 -> 1 万线程
线程一多,就会带来:
- 线程栈内存
- 上下文切换
- 调度器压力
- 慢连接占用线程
Netty 不是这个模型。
Netty 常见服务端线程模型是:
BossGroup
负责 accept 新连接
WorkerGroup
负责连接上的 read/write 事件
每个 group 里面有多个 EventLoop。
每个 EventLoop 通常对应一个线程。
一个连接 Channel 注册到某个 EventLoop 后,后续这个 Channel 的 IO 事件通常都由这个 EventLoop 处理。
大概是:
Channel-1 -> EventLoop-1
Channel-2 -> EventLoop-1
Channel-3 -> EventLoop-2
Channel-4 -> EventLoop-2
...
所以 Netty 的模型是:
大量连接
少量 EventLoop 线程
事件就绪后处理
这和 epoll 的思想是一致的:
- 不是每个连接一个线程等;
- 而是少量线程统一等大量连接事件。
五、EventLoop 是 Netty 的心脏
如果只选一个 Netty 核心概念,那一定是:
EventLoop
EventLoop 可以粗略理解成:
一个线程 + 一个 Selector + 一个任务队列 + 一个 while 循环。
它大概一直在做几件事:
- 等待 IO 事件
- 处理 selected keys
- 执行普通任务
- 执行定时任务
- 继续下一轮循环
伪代码类似:
java
while (true) {
select();
processSelectedKeys();
runAllTasks();
}
当然真实源码比这个复杂很多,但主干就是这样。
这也解释了一个非常重要的规则:
不要在 EventLoop 线程里执行阻塞操作。
因为一个 EventLoop 线程通常负责多个 Channel。
如果你在某个 Handler 里写了阻塞代码:
java
Thread.sleep(1000);
或者同步调用一个很慢的数据库接口,那么这个 EventLoop 线程负责的其他连接也会被拖住。
所以 Netty 的高性能不是魔法。
它依赖一个前提:
IO 线程不能被阻塞。
这也是 WebFlux、Reactor Netty、Spring Cloud Gateway 里反复强调的原则。
六、ChannelPipeline:把 IO 事件变成业务处理
原生 NIO 里,Selector 返回事件后,你需要自己判断:
- 这是 accept?
- 这是 read?
- 这是 write?
- 读到的数据怎么解码?
- 解码后怎么交给业务?
- 业务返回后怎么编码?
- 怎么写回 socket?
Netty 用 Pipeline 把这件事抽象出来。
每个 Channel 都有一个 ChannelPipeline。
Pipeline 里有一组 Handler:
ChannelPipeline
Decoder
BusinessHandler
Encoder
Socket
入站事件,例如读取数据,会经过 inbound handler:
socket read
ByteBuf
Decoder
业务 Handler
出站事件,例如写响应,会经过 outbound handler:
业务对象
Encoder
ByteBuf
socket write
Pipeline 的价值是:
把网络事件、协议编解码、业务处理拆开。
这也是 Netty 非常适合做自定义协议、RPC、网关和长连接服务的原因。
七、ByteBuf:高性能网络框架绕不开内存管理
很多人学 Netty 时只关注线程模型和 EventLoop,但 Netty 的性能还有一大块来自内存管理。
Java 原生 NIO 用的是:
ByteBuffer
但 ByteBuffer 用起来有不少别扭的地方:
- 读写模式要 flip
- API 不够顺手
- 扩容不方便
- 池化能力不够统一
- 引用和释放不够清晰
Netty 自己设计了:
ByteBuf
ByteBuf 的优势包括:
- readerIndex / writerIndex 分离
- 支持堆内存和直接内存
- 支持池化分配
- 支持引用计数
- 更适合网络读写
为什么池化重要?
因为高并发网络系统会频繁读写数据。
如果每次读写都创建新的 byte[] 或 ByteBuffer,会带来:
- 频繁内存分配
- GC 压力
- 内存碎片
- 吞吐抖动
Netty 通过 ByteBuf 和 PooledByteBufAllocator,把内存管理也纳入了高性能设计。
所以 Netty 不只是 IO 框架,也是一个重视内存效率的框架。
八、Netty 如何处理半包粘包?
TCP 是字节流协议。
它不保证应用层消息边界。
你发送两条消息:
helloworld
对方可能一次读到:
helloworld
也可能分几次读到:
hello world
这就是常说的:
- 半包
- 粘包
原生 NIO 里,你需要自己处理这些问题。
Netty 提供了一套编解码器机制,例如:
ByteToMessageDecoderMessageToByteEncoderLengthFieldBasedFrameDecoderLineBasedFrameDecoderDelimiterBasedFrameDecoder
这些组件让你可以把 TCP 字节流转换成应用层消息。
比如基于长度字段的协议:
消息长度 + 消息内容
就可以用 LengthFieldBasedFrameDecoder 来处理。
这也是 Netty 的工程价值之一:
- 它不只是帮你收发字节,
- 还帮你建立协议处理模型。
九、writeAndFlush 不是马上写到网卡
很多人刚用 Netty 时会以为:
java
ctx.writeAndFlush(response);
这行代码执行完,数据就已经发到对端了。
其实不是。
更准确地说:
writeAndFlush 表示把数据写入 Netty 的出站流程,并尝试 flush 到 socket。
但 socket 是否能立刻写完,要看:
- 内核发送缓冲区是否有空间
- 对端接收是否足够快
- 当前 Channel 是否可写
- 数据量是否很大
如果写不完,Netty 需要把待写数据放在写队列里。
后续等 socket 再次可写时继续写。
这里会涉及:
ChannelOutboundBufferWriteBufferWaterMarkisWritablechannelWritabilityChanged
这就是背压的基础。
如果不理解这点,就很容易在高并发场景里写出危险代码:
- 不断 writeAndFlush
- 不关心写队列堆积
- 不关心对端是否慢
- 最终内存暴涨
所以 Netty 的写出流程也是专题里必须单独讲的一篇。
十、Netty 和 Reactor Netty / WebFlux / Gateway 的关系
现在回到 Spring 生态。
这里要先补一个边界:
- Spring WebFlux 本身是响应式 Web 编程模型,不等于 Netty。
- WebFlux 可以运行在 Reactor Netty、Tomcat、Jetty、Undertow 等运行时上。
- 但 Spring Cloud Gateway 的经典 WebFlux 版本默认依赖 Reactor Netty。
所以更准确地说:
- WebFlux 不一定底层是 Netty;
- 但 Reactor Netty 和 Spring Cloud Gateway 这条链路,确实是站在 Netty 之上的。
Spring Cloud Gateway 的典型依赖链路是:
Spring Cloud Gateway
Spring WebFlux
Project Reactor
Reactor Netty
Netty
Gateway 的核心工作是:
- 接收客户端请求
- 路由匹配
- 执行过滤器
- 用 Netty HttpClient 转发到下游
- 把下游响应写回客户端
所以它非常适合基于 Netty 的事件驱动模型。
WebFlux 是上层编程模型。
Reactor 是响应式流框架。
Reactor Netty 是把 Reactor 和 Netty 连接起来的网络运行时。
Netty 则负责底层:
- 连接
- 事件循环
- 读写
- 内存
- 编解码
所以如果你想真正理解 Spring Cloud Gateway 的性能模型,就不能只停留在:
WebFlux 是响应式的。
还要往下看:
- Reactor Netty 如何使用 Netty EventLoop?
- Netty EventLoop 如何管理 Channel?
- ChannelPipeline 如何处理请求?
- writeAndFlush 如何写回响应?
这就是为什么学 Netty 对理解 WebFlux 和 Gateway 很有价值。
十一、学 Netty 应该抓住哪些源码主线?
Netty 源码很大,不建议一开始全量阅读。
更好的方式是抓主线。
第一条:服务端启动流程。
ServerBootstrap.bind()
AbstractBootstrap.doBind()
initAndRegister()
channelFactory.newChannel()
NioServerSocketChannel
ChannelInitializer
第二条:线程模型。
NioEventLoopGroup
NioEventLoop
SingleThreadEventExecutor
EventLoopChooser
第三条:EventLoop 核心循环。
NioEventLoop.run()
select()
processSelectedKeys()
runAllTasks()
第四条:Pipeline 事件传播。
DefaultChannelPipeline
AbstractChannelHandlerContext
fireChannelRead
write
flush
第五条:写出流程。
writeAndFlush
ChannelOutboundBuffer
doWrite
socket
第六条:编解码。
ByteToMessageDecoder
callDecode
cumulation
LengthFieldBasedFrameDecoder
把这几条主线看懂,Netty 的骨架就立起来了。
十二、结论:Netty 是 Java 高并发网络系统的底座
最后总结一下。
Java 原生 NIO 提供了底层能力:
ChannelSelectorByteBuffer
但原生 NIO 太底层,直接写复杂网络系统会遇到很多工程问题:
- 线程模型
- 事件分发
- 内存管理
- 协议拆包
- 写队列
- 背压
- 异常处理
- 资源释放
Netty 的价值在于:
把这些复杂问题组织成一套清晰的工程模型。
这套模型就是:
EventLoopChannelPipelineByteBufCodecOutboundBuffer
如果说 epoll 解决的是:
如何高效等待大量连接事件。
如果说 Nginx 解决的是:
如何把操作系统能力工程化成一个高性能服务器。
那么 Netty 解决的是:
如何把 Java NIO 工程化成一个可扩展的高并发网络框架。
所以学习 Netty 的意义,不只是会写几行网络代码。
而是看懂 Java 世界里很多高性能框架的底层共同语言:
- 事件循环
- 非阻塞 IO
PipelineByteBuf- 背压
- 编解码
理解了 Netty,再回头看 WebFlux、Reactor Netty、Spring Cloud Gateway,就不会只看到"响应式"这几个字,而能看到它们下面真正工作的网络引擎。
对我的架构判断有什么用?
如果再回到无人机和机库这类业务系统,Netty 给我的启发也不只是"用不用 Netty"。
更重要的是形成一组架构判断:
- 看设备接入链路时,不只看接口,而要看连接生命周期和事件调度;
- 看 MQTT 消息链路时,不只看 topic,而要看消息进入应用后的处理路径;
- 看视频链路时,不只看协议格式,而要看持续流量、慢客户端和写出压力;
- 看 Web 播放和网关转发时,不只看请求是否成功,而要看下游慢时系统如何退让;
- 看高并发系统时,不先问线程开多少,而先问事件、任务和阻塞点如何被隔离。
这才是 Netty 对业务架构师真正有价值的地方:
它把高并发系统的复杂性,拆成可以被观察、设计和治理的几个核心对象。
下一篇我们就从:
ServerBootstrap.bind()
开始,看 Netty 服务端启动时:
- Channel 是如何创建的?
- Pipeline 是什么时候初始化的?
- ServerSocketChannel 是如何注册到 EventLoop 的?
- 端口 bind 最终发生在哪里?
也就是沿着服务端启动流程,正式进入 Netty 源码主线。