ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Netty 详细讲解

Netty 详细讲解 目录一、BIO / NIO / AIO 简单对比Java NIO 三大组件二、Netty 核心架构与线程模型重中之重主从 Reactor 模型NioEventLoop三、Channel、Pipeline、ChannelHandler核心三件套1. Channel2. ChannelPipeline 流水线3. ChannelHandler 处理器ByteBufNetty 封装的缓冲区替代 JDK ByteBuffer四、编解码器解决粘包半包五、Netty 重要概念1. ChannelFuture2. ChannelOption / ChildOption3. IdleStateHandler 空闲检测业务心跳4. 零拷贝 Zero‑Copy六、Netty 执行完整流程服务端接收消息全过程七、常见坑面试高频八、Netty 的 Reactor 面试高频问题九、简单完整示例服务端十、Netty 适合什么场景Netty 是基于 Java NIO 的高性能、异步事件驱动的网络通信框架封装了 Java 原生 NIO 的复杂 API用于快速开发高并发的 TCP/UDP 网络服务端、客户端。主流中间件底层网络全部用 NettyRocketMQ、Dubbo、Elasticsearch、Zookeeper、Spring Cloud Gateway、Redis Java 客户端。Java 网络演进BIO → NIO(Java原生) → Netty(封装NIO)一、BIO / NIO / AIO 简单对比模型IO 类型线程模型缺点BIO阻塞 IO1 连接 1 线程高并发下线程爆炸上下文切换重NIO(Java)非阻塞同步单线程 Selector 轮询原生 API 繁琐BUG 多 (空轮询 bug)AIO异步非阻塞操作系统回调Windows 好Linux 内核支持差实际很少用Netty 底层在 Linux 上使用 NIOEpollWindows 上会自动切换 AIO。生产环境几乎都是 NIO 模式。Java NIO 三大组件Buffer 缓冲区数据读写容器ByteBufferChannel 通道双向读写SocketChannel、ServerSocketChannelSelector 选择器多路复用器一个线程监听大量 Channel 事件连接、读、写实现少量线程处理上万连接。原生 NIO 痛点ByteBuffer API 难用需要 flip、rewind、clear容易内存溢出Selector 空轮询 BUG (JDK bug)CPU 100%没有编解码器、断连处理、心跳、半包粘包全部自己手写线程模型、异常处理需要自己实现 Netty 就是把这些坑全部封装好。二、Netty 核心架构与线程模型重中之重Netty 核心线程模型Reactor 反应器模式Netty 实现了三种 Reactor单线程 Reactor不生产用一个线程干接受连接 处理 IO多线程 Reactor一个 accept 线程N 个 IO 线程处理读写主从 ReactorNetty 默认【生产常用】主从 Reactor 模型Boss Group主 Reactor负责接收 TCP 连接只做 accept不处理业务读写。一般 1 个线程足够。Worker Group从 Reactor处理已建立连接的所有 IO 事件read、write、编解码、触发 handlerN 个线程。NioEventLoopGroup 就是线程组NioEventLoop 就是单个线程。java// 服务端经典初始化代码 // boss线程组接收连接worker线程组处理socket读写 NioEventLoopGroup bossGroup new NioEventLoopGroup(1); NioEventLoopGroup workerGroup new NioEventLoopGroup(); ServerBootstrap bootstrap new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 指定NIO通道实现 .option(ChannelOption.SO_BACKLOG,128) // tcp半连接队列 .childOption(ChannelOption.SO_KEEPALIVE,true) // tcp心跳 .childHandler(new ChannelInitializerNioSocketChannel() { Override protected void initChannel(NioSocketChannel ch) { // 流水线添加处理器 ch.pipeline().addLast(new MyHandler()); } }); ChannelFuture future bootstrap.bind(8888).sync(); future.channel().closeFuture().sync();NioEventLoop一个NioEventLoop对应一个 Java 线程 一个 Selector一个 Worker NioEventLoop 可以绑定多个 SocketChannel多路复用监听 IO 事件一个 SocketChannel 永远只会绑定到同一个 NioEventLoop 线程所以同一个 channel 的 handler 执行是单线程不需要同步锁非常关键的特性任务队列每个 NioEventLoop 有任务队列支持普通任务、定时任务。注意不要在 Netty IO 线程执行耗时业务数据库查询、http 调用会阻塞整个线程所有绑定这个线程的连接全部卡死 耗时业务丢到自定义业务线程池执行。三、Channel、Pipeline、ChannelHandler核心三件套1. Channel代表一条 Socket 连接封装底层 Java NIO SocketChannel。 常用方法writeAndFlush()写消息并刷到内核缓冲区close()关闭连接channel().isActive()判断连接是否存活2. ChannelPipeline 流水线每个 Channel 内部持有一个ChannelPipeline是Handler 的双向链表。 IO 事件会沿着 pipeline 依次流转。inbound 入站事件外部 → 服务端连接建立、读数据从 head→tail 执行 outbound 出站事件服务端 → 外部写数据、关闭从 tail→head 执行HeadContext -- Handler1 -- Handler2 -- TailContext3. ChannelHandler 处理器分为两类ChannelInboundHandler 入站处理器处理接收过来的数据channelRead()读取报文ChannelOutboundHandler 出站处理器处理向外发送的数据write()常用适配器简化开发ChannelInboundHandlerAdapterChannelOutboundHandlerAdapterSimpleChannelInboundHandler自动释放 ByteBuf泛型直接拿到解码后的对象业务 handler 最常用ByteBufNetty 封装的缓冲区替代 JDK ByteBufferNetty 自己实现的缓冲区解决原生 ByteBuffer 的痛点读写指针分离readerIndex、writerIndex不用 flip 切换支持池化内存PooledByteBuf默认开启复用直接内存减少 GC支持复合缓冲区 CompositeByteBuf组合多个缓冲区不拷贝堆内存 (HeapByteBuf) / 直接内存 (DirectByteBuf零拷贝不走 JVM 堆)⚠️内存释放Netty 引用计数retain()/release()DirectByteBuf 不释放会直接内存泄漏 SimpleChannelInboundHandler 会自动 release普通 InboundHandler 拿到 ByteBuf处理完必须手动 release。四、编解码器解决粘包半包TCP 是流协议没有消息边界会出现粘包、半包多条报文粘在一起或者一条报文被拆成多次到达。Netty 内置开箱即用解码器FixedLengthFrameDecoder固定长度帧DelimiterBasedFrameDecoder分隔符分割报文比如换行符LengthFieldBasedFrameDecoder【最常用】长度域解码器协议头部存消息长度支持自定义偏移RPC、MQ 大量使用。解码器属于 InboundHandler编码器属于 OutboundHandler。 编码业务对象 → ByteBuf解码ByteBuf → 业务对象。示例Protobuf、JSON 都可以配合 LengthFieldBasedFrameDecoder 做私有二进制协议。五、Netty 重要概念1. ChannelFutureNetty 所有 IO 操作全是异步bind、connect、writeAndFlush 全部返回 ChannelFuture不会阻塞。.sync()阻塞等待操作完成.addListener()异步回调推荐不阻塞线程java// 异步回调写法 channel.writeAndFlush(msg).addListener(future - { if(future.isSuccess()){ System.out.println(发送成功); }else{ future.cause().printStackTrace(); } });2. ChannelOption / ChildOptionOption作用于服务端 ServerSocketChannel接受连接的那个 socketChildOption作用于每一条客户端 SocketChannel常用参数SO_BACKLOGTCP 半连接队列大小SO_KEEPALIVETCP 层面的空闲探测不推荐业务心跳粒度粗TCP_NODELAY关闭 Nagle 算法小报文立刻发送低延迟系统开启3. IdleStateHandler 空闲检测业务心跳TCP keepalive 是内核层业务感知差。Netty 提供空闲处理器放在 pipeline检测读空闲、写空闲触发事件我们发送心跳包超时关闭僵死连接。java// 读空闲5秒写空闲8秒读写空闲10秒触发IdleStateEvent ch.pipeline().addLast(new IdleStateHandler(5,8,10));4. 零拷贝 Zero‑CopyNetty 的零拷贝不是操作系统 mmap 那种分几层FileRegion文件传输直接把文件从磁盘发送到网卡不拷贝到 JVM 内存CompositeByteBuf逻辑合并多个缓冲区不做内存复制Direct Buffer使用操作系统直接内存JVM 堆和 socket 之间少一次拷贝六、Netty 执行完整流程服务端接收消息全过程BossGroup NioEventLoop 的 Selector 监听 OP_ACCEPT 事件有客户端连接进来accept 得到 SocketChannel将 SocketChannel 注册到 WorkerGroup 其中一个 NioEventLoop 的 Selector 上监听 OP_READ 事件客户端发送数据Worker 线程 Selector 触发 OP_READ读到字节到 ByteBuf进入 pipeline从 head 开始执行各个 InboundHandler首先经过解码器LengthFieldBasedFrameDecoder拆包把 ByteBuf 转成业务 POJO 对象执行业务 Handler channelRead () 方法如果业务要返回响应调用 writeAndFlush事件反向走 OutboundHandler编码器编码最终发送到 socket 缓冲区write () 只是写入缓冲区不会真正发送必须调用 flush 才会刷出writeAndFlush 合并两个操作。七、常见坑面试高频禁止在 IO 线程执行耗时操作数据库、RPC、复杂计算会阻塞 NioEventLoop 线程同线程所有连接卡死。解决方案把任务提交到自定义业务线程池。DirectByteBuf 内存泄漏直接内存不在堆GC 不会自动回收必须 release开启-Dio.netty.leakDetection.levelPARANOID检测内存泄漏。粘包半包不要裸用 ByteBuf 接收业务报文必须加解码器。不要混用同步 sync ()IO 线程内调用 sync () 造成死锁IO 线程中绝对不能调用 future.sync ()要用 addListener 异步回调。NioEventLoop 绑定 Channel 后channel 所有 handler 都在这个线程执行所以同一个 channel 内变量不需要加 synchronized跨多个 channel 共享变量仍然需要锁。TCP 断开不一定触发 channelRead一定要捕获channelInactive连接断开事件清理会话。八、Netty 的 Reactor 面试高频问题QBossGroup 线程数为什么一般设置 1 ServerSocketChannel 只有一个只负责 accept 接受连接单线程足够多线程反而会竞争 accept 锁没有收益。QWorkerGroup 默认线程数CPU核心数*2Netty 默认构造器就是这个值。QNetty 怎么解决 JDK NIO Selector 空轮询 bug 检测 Selector.select () 返回 0 但没有 IO 事件达到阈值就重建新 Selector把所有 channel 迁移过去。QwriteAndFlush 发送消息是立刻发送给客户端吗 不是写到 Netty 的发送缓冲区队列由 NioEventLoop 在合适时机调用底层 socket write 系统调用刷入操作系统内核 socket 缓冲区内核缓冲区再发给网络。九、简单完整示例服务端javapublic class NettyServerDemo { public static void main(String[] args) throws InterruptedException { NioEventLoopGroup boss new NioEventLoopGroup(1); NioEventLoopGroup worker new NioEventLoopGroup(); try { ServerBootstrap b new ServerBootstrap(); b.group(boss, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializerNioSocketChannel() { Override protected void initChannel(NioSocketChannel ch) { ChannelPipeline p ch.pipeline(); // 简单按换行符分割报文 p.addLast(new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter())); p.addLast(new StringDecoder()); p.addLast(new StringEncoder()); // 业务handler p.addLast(new SimpleChannelInboundHandlerString() { Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println(收到客户端消息 msg); ctx.writeAndFlush(服务端回执 msg \r\n); } Override public void channelInactive(ChannelHandlerContext ctx) { System.out.println(客户端断开连接); } }); } }); ChannelFuture f b.bind(9000).sync(); f.channel().closeFuture().sync(); } finally { boss.shutdownGracefully(); worker.shutdownGracefully(); } } }十、Netty 适合什么场景✅ 长连接服务、RPC 框架、消息中间件、网关、IM 聊天、私有二进制协议 ❌ 简单短连接 http 接口直接用 SpringBoot web 即可没必要手写 Netty如果你需要我可以继续手写 Netty 客户端完整代码LengthFieldBasedFrameDecoder 详细图解协议头长度域模拟半包粘包演示Netty 高频面试题整理Netty 源码核心流程梳理NioEventLoop.run 循环
返回列表