【Netty源码解读和权威指南】第31篇:Netty零拷贝深度解析——性能极致的秘密武器

📅 2026/6/20 9:52:33
【Netty源码解读和权威指南】第31篇:Netty零拷贝深度解析——性能极致的秘密武器
上一篇【第30篇】Netty写数据源码解析——write/flush背后的双队列设计下一篇【第32篇】Netty背压机制——不让发送方撑死接收方开篇故事某文件传输系统传输1GB文件CPU飙到90%。排查每次发送都要memcpy从堆内拷贝到堆外优化后使用FileRegion.TransferTo直接DMA传输CPU降到5%零拷贝 不进行CPU参与的memcpy一、传统vs零拷贝对比传统方式4次拷贝2次上下文切换 磁盘→内核缓冲区→用户缓冲区→Socket缓冲区→网卡 ↑ DMA ↑ CPU memcpy ↑ CPU memcpy ↑ DMA 零拷贝sendfile2次拷贝0次上下文切换 磁盘→内核缓冲区→Socket缓冲区→网卡 ↑ DMA ↑ DMA(仅描述符传递)二、Netty四种零拷贝实现2.1 CompositeByteBuf——组合多个ByteBufByteBufheaderUnpooled.copiedBuffer(HTTP/1.1 200 OK\r\n.getBytes());ByteBufbodyUnpooled.copiedBuffer(Hello Netty.getBytes());CompositeByteBufcompositeUnpooled.compositeBuffer();composite.addComponents(true,header,body);// 零拷贝header和body的数据没有复制只保存了引用2.2 slice()——切片共享同一块内存ByteBufbufUnpooled.buffer(1024);buf.writeBytes(Hello World.getBytes());ByteBufslicebuf.slice(0,5);// Hello 零拷贝slice.setByte(0,h);// 修改slice也影响原buf2.3 wrap()——包裹字节数组byte[]dataHello.getBytes();ByteBufbufUnpooled.wrappedBuffer(data);// 零拷贝包裹2.4 FileRegion——文件传输零拷贝// 直接将文件数据DMA传输到Socket不经过用户空间RandomAccessFilefilenewRandomAccessFile(largefile.dat,r);FileRegionregionnewDefaultFileRegion(file.getChannel(),0,file.length());ctx.writeAndFlush(region);三、FileRegion源码publicclassDefaultFileRegionextendsAbstractReferenceCountedimplementsFileRegion{privatefinalFileChannelfile;privatefinallongposition;privatefinallongcount;publiclongtransferTo(WritableByteChanneltarget,longpos)throwsIOException{returnfile.transferTo(this.positionpos,count-pos,target);}}// NioSocketChannel中使用protectedbooleandoWriteFileRegion(FileRegionregion){longwrittenregion.transferTo(javaChannel(),region.transferred());// 底层调用FileChannel.transferTo() → sendfile()系统调用}四、实战高性能文件服务器importio.netty.bootstrap.ServerBootstrap;importio.netty.channel.*;importio.netty.channel.nio.NioEventLoopGroup;importio.netty.channel.socket.nio.NioServerSocketChannel;importio.netty.handler.codec.LineBasedFrameDecoder;importio.netty.handler.codec.string.StringDecoder;importjava.io.RandomAccessFile;publicclassZeroCopyFileServer{publicstaticvoidmain(String[]args)throwsException{EventLoopGroupbossnewNioEventLoopGroup(1);EventLoopGroupworkernewNioEventLoopGroup();try{newServerBootstrap().group(boss,worker).channel(NioServerSocketChannel.class).childHandler(newChannelInitializerChannel(){protectedvoidinitChannel(Channelch){ch.pipeline().addLast(newLineBasedFrameDecoder(1024));ch.pipeline().addLast(newStringDecoder());ch.pipeline().addLast(newFileSendHandler());}}).bind(8080).sync().channel().closeFuture().sync();}finally{boss.shutdownGracefully();worker.shutdownGracefully();}}staticclassFileSendHandlerextendsChannelInboundHandlerAdapter{publicvoidchannelRead(ChannelHandlerContextctx,Objectmsg){Stringpath((String)msg).trim();try{RandomAccessFilefilenewRandomAccessFile(path,r);// 零拷贝发送文件ctx.write(newDefaultFileRegion(file.getChannel(),0,file.length()));ctx.writeAndFlush(\r\n);}catch(Exceptione){ctx.close();}}}}五、性能对比方式CPU内存拷贝次数适用场景传统read/write90%4次小文件HeapBuf write60%3次中等文件DirectBuf write30%2次大文件FileRegion5%0次DMA超大文件六、总结方式原理CompositeByteBuf多个ByteBuf零拷贝拼接slice()同一块内存的不同视图wrap()byte[]零拷贝包裹FileRegionsendfile()系统调用DMA传输上一篇【第30篇】Netty写数据源码解析——write/flush背后的双队列设计下一篇【第32篇】Netty背压机制——不让发送方撑死接收方