Netty高性能的缘故原由
Netty作为异步事宜驱动的网络框架,高性能紧张来自于其I/O模型和线程处理模型,前者决定如何收发数据,后者决定如何处理数据。

Netty高性能的缘故原由总结,智者见智,并没有固定答案。
基于I/O多路复用模型零拷贝基于NIO的Buffer基于内存池的缓冲区重用机制无锁化的串行设计理念I/O操作的异步处理供应对protobuf等高性能序列化协议支持可以对TCP进行更加灵巧地配置Netty的零拷贝
在操作系统层面上的零拷贝是指避免在用户态与内核态之间来回拷贝数据的技能。 Netty中的零拷贝与操作系统层面上的零拷贝不完备一样, Netty的零拷贝完备是在用户态(Java层面)的,更多是数据操作的优化。
Netty的零拷贝紧张表示在五个方面
Netty的吸收和发送ByteBuffer利用直接内存进行Socket读写,不须要进行字节缓冲区的二次拷贝。如果利用JVM的堆内存进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。比较于利用直接内存,在发送过程中多了一次缓冲区的内存拷贝。Netty的文件传输调用FileRegion包装的transferTo方法,可以直接将文件缓冲区的数据发送到目标Channel,避免通过循环write办法导致的内存拷贝问题。Netty供应CompositeByteBuf类, 可以将多个ByteBuf合并为一个逻辑上的ByteBuf, 避免了各个ByteBuf之间的拷贝。通过wrap操作, 我们可以将byte[]数组、ByteBuf、ByteBuffer等包装成一个Netty ByteBuf工具, 进而避免拷贝操作。ByteBuf支持slice操作,可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf, 避免内存的拷贝。有关第1条,NIO的零拷贝与直接内存映射详解,可以 ref=\"大众https://mp.weixin.qq.com/s?__biz=MzUyNzgyNzAwNg==&mid=2247483933&idx=1&sn=d9776b9efe054b30523adbe60cb7524a&scene=21#wechat_redirect\"大众>点击查看 ,本文不作讲解。
通过FileRegion实现零拷贝
基于上一篇博客的知识,理解Netty的零拷贝就很随意马虎。
FileRegion底层调用NIO FileChannel的transferTo函数。 下面的代码节选自netty源码中example包的FileServerHandler.java。
@Overridepublic void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { RandomAccessFile raf = null; long length = -1; try { // 1. 通过 RandomAccessFile 打开一个文件. raf = new RandomAccessFile(msg, \公众r\"大众); length = raf.length(); } catch (Exception e) { ctx.writeAndFlush(\公众ERR: \公众 + e.getClass().getSimpleName() + \公众: \"大众 + e.getMessage() + '\n'); return; } finally { if (length < 0 && raf != null) { raf.close(); } } ctx.write(\公众OK: \公众 + raf.length() + '\n'); if (ctx.pipeline().get(SslHandler.class) == null) { // SSL not enabled - can use zero-copy file transfer. // 2. 调用 raf.getChannel() 获取一个 FileChannel. // 3. 将 FileChannel 封装成一个 DefaultFileRegion ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length)); } else { // SSL enabled - cannot use zero-copy file transfer. ctx.write(new ChunkedFile(raf)); } ctx.writeAndFlush(\"大众\n\"大众);}
通过CompositeByteBuf实现零拷贝
CompositeByteBuf可以把须要合并的多个bytebuf组合起来,对外供应统一的readIndex和writerIndex。 但在CompositeByteBuf内部, 合并的多个ByteBuf都是单独存在的,CompositeByteBuf 只是逻辑上是一个整体。
CompositeByteBuf里面有个Component数组,聚合的bytebuf都放在Component数组里面,最小容量为16。
传统做法合并ByteBuf
假设有一份协议数据,它由头部和体组成,而头部和体是分别存放在两个ByteBuf中的, 为了方便后续处理,要将两个ByteBuf进行合并。
ByteBuf header = ...ByteBuf body = ...// 按照原来的做法 将header和body合并为一个ByteBufByteBuf allBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes());allBuf.writeBytes(header);allBuf.writeBytes(body);
上述过程将header和body都拷贝到了新的allBuf中,这增加了两次额外的数据拷贝操作了。
CompositeByteBuf实现合并bytebuf
CompositeByteBuf合并ByteBuf,减少两次额外的数据拷贝操作。
ByteBuf header = ...ByteBuf body = ...// 新建CompositeByteBuf工具CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();// 第一个参数是true, 表示当添加新的ByteBuf时, 自动递增 CompositeByteBuf 的 writeIndex。如果不传第一个参数或第一个参数为false,则合并后的compositeByteBuf的writeIndex不移动,即不能从compositeByteBuf中读取到新合并的数据。compositeByteBuf.addComponents(true,header,body);
一张图清楚理解readIndex和writeIndex。
除了上面直策应用CompositeByteBuf类外, 还可以利用 Unpooled.wrappedBuffer方法。 Unpooled封装了CompositeByteBuf的操作,利用起来更加方便:
ByteBuf header = ...ByteBuf body = ...ByteBuf allByteBuf = Unpooled.wrappedBuffer(header, body);
通过wrap操作实现零拷贝
如果将一个byte数组转换为一个ByteBuf工具,以便于后续的操作,那么传统的做法是将此byte数组拷贝到ByteBuf中。
byte[] bytes = ...ByteBuf byteBuf = Unpooled.buffer();byteBuf.writeBytes(bytes);
显然这样的办法也是有一个额外的拷贝操作的, 我们可以利用Unpooled的干系方法, 包装这个byte数组, 天生一个新的ByteBuf实例, 而不须要进行拷贝操作. 上面的代码可以改为:
byte[] bytes = ...ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);
通过Unpooled.wrappedBuffer方法将bytes包装为一个UnpooledHeapByteBuf工具, 而在包装的过程中, 不会有拷贝操作的,即天生的ByteBuf工具是和bytes数组共用了同一个存储空间,对bytes的修正也便是对ByteBuf工具的修正。
Unpooled类还供应了很多重载的wrappedBuffer方法,将一个或多个buffer包装为一个 ByteBuf工具,从而实现零拷贝。
public static ByteBuf wrappedBuffer(byte[] array)public static ByteBuf wrappedBuffer(byte[] array, int offset, int length)public static ByteBuf wrappedBuffer(ByteBuffer buffer)public static ByteBuf wrappedBuffer(ByteBuf buffer)public static ByteBuf wrappedBuffer(byte[]... arrays)public static ByteBuf wrappedBuffer(ByteBuf... buffers)public static ByteBuf wrappedBuffer(ByteBuffer... buffers)public static ByteBuf wrappedBuffer(int maxNumComponents, byte[]... arrays)public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuf... buffers)public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuffer... buffers)
通过slice操作实现零拷贝
slice操作和wrap操作刚好相反, Unpooled.wrappedBuffer可以将多个ByteBuf 合并为一个, 而slice操作可以将一个ByteBuf切片为多个共享一个存储区域的 ByteBuf工具。
ByteBuf供应了两个slice操作方法:
public ByteBuf slice();public ByteBuf slice(int index, int length);
前者等同于buf.slice(buf.readerIndex(), buf.readableBytes())调用,即返回buf中可读部分的切片。
后者相对就比较灵巧,可以设置不同的参数获取buf不同区域的切片。
下面的例子展示了ByteBuf.slice方法的大略用法:
ByteBuf byteBuf = ...ByteBuf header = byteBuf.slice(0, 5);ByteBuf body = byteBuf.slice(5, 10);
用slice方法产生header和body的过程是没有拷贝操作的,header和body工具在内部实在是共享了byteBuf存储空间的不同部分而已 。
须要java学习路线图的私信笔者“java”领取哦!
其余喜好这篇文章的可以给笔者点个赞许,关注一下,每天都会分享Java干系文章!
还有禁绝时的福利赠予,包括整理的学习资料,口试题,源码等~~









