作为Java网络编程学习者,不仅要知道NIO,还一定要学习Mina和Netty这两个优秀的网络框架。作为上一篇NIO效率高的原理之零拷贝与直接内存映射的补充,本文将针对Netty的零拷贝特性进行详细分析。

Netty高性能的原因

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

Netty高性能的原因总结,智者见智,并没有固定答案。

  1. 基于I/O多路复用模型

  2. 零拷贝

  3. 基于NIO的Buffer

  4. 基于内存池的缓冲区重用机制

  5. 无锁化的串行设计理念

  6. I/O操作的异步处理

  7. 提供对protobuf等高性能序列化协议支持

  8. 可以对TCP进行更加灵活地配置

Netty的零拷贝

在操作系统层面上的零拷贝是指避免在用户态与内核态之间来回拷贝数据的技术。Netty中的零拷贝与操作系统层面上的零拷贝不完全一样, Netty的零拷贝完全是在用户态(Java层面)的,更多是数据操作的优化。

Netty的零拷贝主要体现在五个方面

  1. Netty的接收和发送ByteBuffer使用直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用JVM的堆内存进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于使用直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

  2. Netty的文件传输调用FileRegion包装的transferTo方法,可以直接将文件缓冲区的数据发送到目标Channel,避免通过循环write方式导致的内存拷贝问题。

  3. Netty提供CompositeByteBuf类, 可以将多个ByteBuf合并为一个逻辑上的ByteBuf, 避免了各个ByteBuf之间的拷贝。

  4. 通过wrap操作, 我们可以将byte[]数组、ByteBuf、ByteBuffer等包装成一个Netty ByteBuf对象, 进而避免拷贝操作。

  5. ByteBuf支持slice操作,可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf, 避免内存的拷贝。

有关第1条,NIO的零拷贝与直接内存映射详解,可以点击查看,本文不作讲解。

通过FileRegion实现零拷贝

基于上一篇博客的知识,理解Netty的零拷贝就很容易。

FileRegion底层调用NIO FileChannel的transferTo函数。下面的代码节选自netty源码中example包的FileServerHandler.java。

@Override
public 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 封装成一个 DefaultFileRegionctx.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合并为一个ByteBuf
ByteBuf 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存储空间的不同部分而已。

更多内容,欢迎关注微信公众号:全菜工程师小辉~


戳“阅读原文”一起来充电吧!

喜欢就点个“在看”呗^_^

彻底搞懂Netty高性能之零拷贝相关推荐

  1. 浅析操作系统和Netty中的零拷贝机制

    点击关注公众号,Java干货及时送达 零拷贝机制(Zero-Copy)是在操作数据时不需要将数据从一块内存区域复制到另一块内存区域的技术,这样就避免了内存的拷贝,使得可以提高CPU的.零拷贝机制是一种 ...

  2. 理解Netty中的零拷贝(Zero-Copy)机制

    理解Netty中的零拷贝(Zero-Copy)机制 发表于2年前(2014-01-13 15:11)   阅读(10209) | 评论(12) 164人收藏此文章,我要收藏 赞29 12月12日北京O ...

  3. 对于 Netty ByteBuf 的零拷贝(Zero Copy) 的理解

    根据 Wiki 对 Zero-copy 的定义: "Zero-copy" describes computer operations in which the CPU does n ...

  4. netty如何实现零拷贝

    根据 Wiki 对 Zero-copy 的定义: "Zero-copy" describes computer operations in which the CPU does n ...

  5. netty应用场景_彻底搞懂 netty 线程模型

    编者注:Netty是Java领域有名的开源网络库,特点是高性能和高扩展性,因此很多流行的框架都是基于它来构建的,比如我们熟知的Dubbo.Rocketmq.Hadoop等.本文就netty线程模型展开 ...

  6. netty socket超时设置_彻底搞懂 netty 线程模型

    编者注:Netty是Java领域有名的开源网络库,特点是高性能和高扩展性,因此很多流行的框架都是基于它来构建的,比如我们熟知的Dubbo.Rocketmq.Hadoop等.本文就netty线程模型展开 ...

  7. 一文搞懂后台高性能服务器设计的常见套路, BAT 高频面试系列

    微信搜索

  8. netty的零拷贝、架构设计、ByteBuf扩容机制详解

    文章目录 1. netty高并发架构设计精髓 ①:主从.Reactor线程模型 ②:NIO多路复用非阻塞 ③:无锁串行化设计思想 ④:高可用.可扩展架构 ⑤:直接内存和零拷贝 ⑥:ByteBuf内存池 ...

  9. Netty、Kafka中的零拷贝技术到底有多牛?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:rrd.me/ggFBd 零拷贝,从字面意思理解就是数据不需 ...

最新文章

  1. 每周一个 Python 模块 | time
  2. K-means算法在手写体数字图像数据上的使用示例-代码详解
  3. maven常见问题问答 收藏
  4. delphi excel取批注所在的行列_excel技巧教程丨34个常用Excel小技巧,助你玩转职场!...
  5. 双 11 模块 79.34% 的代码是怎样智能生成的?
  6. void readstring( char s[] ); 怎样_char *s和char s[]
  7. linux lamp框架,LAMP架构协同应用的实例——phpMyAdmin
  8. 你真的会发朋友圈吗?
  9. 今日头条架构演进之路
  10. redis - 00 在centos安装
  11. 支持向量机(Support Vector Machine,SVM)
  12. Unity使用tolua框架教程: LuaFramewrk
  13. 淘宝API JAVA 乱码
  14. 【题解】【PTA里的Python题库】7-1 身份证校验_python
  15. 【VUE项目实战】51、商品添加功能(一)
  16. 杰理之音箱版本 SDK200 单音 VCOMO 直推,喇叭没声音问题【篇】
  17. ImportError: cannot import name ‘evaluate‘ from ‘surprise‘解决方案
  18. 安卓机开启开发者选项
  19. 关于串行处理过程中时序的论述
  20. 围绕开放标准改进WSO2 API Manager密钥管理体系结构

热门文章

  1. 「 English 」英语口语练习-持续更新
  2. 计算机如何制作音乐相册,电脑电子相册制作软件 精美的音乐电子相册制作方法...
  3. 共享计算机网络凭据,Win10访问局域网共享文件时提示输入网络凭据的解决方法...
  4. Lustre I/O性能特点与最佳实践
  5. Python - 装机系列63 docker镜像不通 no such host
  6. build path功能详解 在项目上右键》Build path》Config build path
  7. (最详细)VueApp项目实战4 - -详情页
  8. MATLAB闪退,无法打开
  9. 重建大师6.0多源通吃!卫星建模、下视影像三维建模、点云融合建模...
  10. 中小型企业如何选择合适又好用的CRM系统?