什么是Netty,它在网络编程中解决了什么问题?
重点
Netty是一个基于Java NIO的异步事件驱动网络框架,用一句话概括就是:让Java程序员不用再跟底层NIO那套Selector、Channel、Buffer较劲,解决了传统Java网络编程中的一些问题。
- BIO模型时一个连接吃一个线程,连接数上万,线程池直接炸。Netty底层走的是NIO多路复用,1-2个线程就能轮询上万个Channel。
- 就算你自己写NIO,Selector的空轮询Bug、ByteBuffer的读写模式切换、半包粘包处理,全是坑。Netty把这些脏活累活全包了,暴露出来就是一条Pipeline责任链,往里头塞Handler就行。
- 生产环境要的心跳监测、空闲超时、内存池、IP黑白名单这些,Netty全部开箱即用,不用自己造轮子。
你听过的很多中间件底层通信框架用的都是它,RocketMQ的Remoting层、Dubbo的网络传输、Elasticsearch节点间通信、gRPC的Java实现,清一色都用Netty。
Netty在整个技术栈中的位置:
- 上层是各种中间件和业务应用(RocketMQ、Dubbo、Elasticsearch、gRPC)
- 中间是Netty框架(提供Pipeline、EventLoop、ByteBuf、编解码器)
- 底层是Java NIO/Epoll
- 再往下是操作系统内核的I/O多路复用
扩展
从BIO到Netty的演进
Java 最早做网络通信用的是BIO,一个Socket连接配一个线程,代码简单是简单,但上了生产就扛不住。
比如一个IM系统要维持10万个长连接,那就得开10万个线程,光线程栈内存就得吃掉几十GB,系统早崩了。后来JDK1.4引入了NIO,核心思路时一个Selector线程轮询多个Channel,哪个Channel有数据就处理哪个,不用傻等。
理论上很美好,但实际写起来巨痛苦:
- ByteBuffer读写共用一个指针,忘了flip就读一堆垃圾数据
- Selector在Linux上有臭名昭著的epoll空轮询Bug,CPU直接飙到100%
- 半包、粘包、断连重连、编解码全得自己撸
Netty就是在这个背景下诞生的,它在NIO之上封了一整套生产级的网络通信方案,让你只关心业务逻辑。
Reactor 线程模型
Netty的线程模型核心是Reactor模式,经历了三代演进:单线程Reactor、多线程Reactor、主从多线程Reactor。生产环境基本都是主从模式。
主从模式的分工很清晰:
- Boss 线程组只干一件事,接受新链接,然后把链接丢给Worker线程组
- Worker线程组负责读写数据、跑Pipeline里的Handler
典型配置就一个Boss线程配CPU核心数 × 2个 Worker线程
// 主从 Reactor 配置
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class);
Boss线程接收客户端连接请求,然后将连接注册到Worker线程池。Worker线程池中的EventLoop负责处理对应Channel的I/O读写事件,数据经过Channel Pipeline中的一系列Handler(解码器、业务逻辑处理器、编码器)后完成处理。
EventLoop的任务调度
每个EventLoop绑定一个线程,管一批Channel。它的处理逻辑分两种情况:
- 非阻塞的快速操作,比如消息解码、协议解析、简单的内存计算,直接在EventLoop线程里执行完,不切线程,性能拉满
- 遇到数据库查询、远程RPC调用这种可能阻塞及时毫秒甚至几秒的操作,必须扔到单独的业务线程池里跑,否则这个EventLoop管的所有Channel全部卡死
这也是用Netty最容易踩的坑:有人在ChannelHandler里直接写了个数据库查询,测试没问题,一上线几百个请求同事来,整个EventLoop被堵死,表现就是其他连接全部超时。
零拷贝的三层实现
Netty的零拷贝不止操作系统层面那一套,它在三个层面都做了优化:
- CompositeByteBuf把多个Buffer拼成一个逻辑视图,不需要真的把数据拷到一块新内存里。协议解析时经常用到,比如Header和Body分别在两个Buffer里,用Composite直接组分就行
- FileRegion底层调的是操作系统的sendfile系统调用,数据从磁盘到网卡全程不经过用户态,适合大文件传输场景
- DirectBuffer池化复用,堆外内存避免JVM堆内到堆外那次拷贝,再加上池化减少了内存分配释放的开销
为什么中间件都选Netty
归根结底三个原因:
- 开箱即用的协议支持覆盖面极广,HTTP、HTTP/2、WebSocket、DNS、Redis协议都有线程的编解码器,中间件团队不用自己啃RFC文档从零实现
- Pipeline的扩展性很好,框架和业务完全隔离,你要加个限流Handler、监控Handler,往链上挂一个就行,不用动别的代码
- 社区成熟度高,从2004到现在,踩过得坑早就修完了,4.x系列稳定运行在无数生产环境里。

浙公网安备 33010602011771号