零拷贝

我们在Java NIO,Netty,Kafka等框架中经常见到零拷贝,通常作为其性能优异的一个重要表现。

下面从 I/O 的几个概念开始,进而再分析零拷贝。

1、I/O 概念

1.1 缓冲区

缓冲区是所有 I/O 的基础,I/O 讲的无非就是把数据移进或移出缓冲区;进程执行 I/O 操作,就是向操作系统发出请求,让它要么把内核缓冲区的数据排干(写),要么填充内核缓冲区(读)。

下图是一个java进程发起Read请求的流程图:

    1. 进程发起 Read 请求之后,内核接收到 Read 请求之后,会先检查内核空间Read缓冲区中是否已经存在进程所需要的数据,
    • 1.1 如果已经存在,则直接把数据 Copy 给进程的缓冲区;
    • 1.2 如果不存在,内核随即向磁盘控制器DMA发出命令,要求从磁盘读取数据,磁盘控制器DMA把数据直接写入内核 Read 缓冲区;
    1. 接下来就是内核将数据 Copy 到进程的缓冲区;
image.png

如果进程发起 Write 请求,同样需要把用户缓冲区里面的数据 Copy 到内核的 Socket 缓冲区里面,然后再通过 DMA 把数据 Copy 到网卡中,发送出去。

如下图所示:

image.png

从读写过程中可以很明显的看出,每次都需要把内核空间的数据拷贝到用户空间(读),或者把用户空间的数据拷贝到内核空间(写)中,挺浪费空间的。

零拷贝的出现就是为了解决这种问题的

1.2 虚拟内存

所有现代操作系统都使用虚拟内存,使用虚拟的地址取代物理地址,这样做的好处是:

  • 多个虚拟地址可以指向同一个物理内存地址。
  • 虚拟内存空间可大于实际可用的物理地址。

利用第一条特性可以把内核空间地址和用户空间的虚拟地址映射到同一个物理地址,这样 DMA 就可以填充对内核和用户空间进程同时可见的缓冲区了。

大致如下图所示:


image.png

这样就省去了内核与用户空间的往来拷贝,从而可以提升性能。

2、零拷贝实现方式之mmap+write

mmap 是一种内存映射文件的方法(I/O读取),即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系,就是上面所说的虚拟内存。

DMA加载磁盘数据到kernel buffer后,用户buffer和内核缓冲区(kernel buffer)进行映射,数据在用户缓冲区和内核缓存区的copy就能省略。

但是如果我们是直接从磁盘读取数据,然后写入网卡时,还是需要从内核空间kernel buffer 把数据copy 到 内核空间socket buffer。

mmap 把文件映射到用户空间里的虚拟内存,省去了从内核缓冲区复制到用户空间的过程,文件中的位置在虚拟内存中有了对应的地址,可以像操作内存一样操作这个文件,相当于已经把整个文件放入内存,但在真正使用到这些数据前却不会消耗物理内存,也不会有读写磁盘的操作,只有真正使用这些数据时,才会将这些数据copy到内核缓存区。

应用程序调用了mmap()之后,数据会先通过DMA拷贝到操作系统内核的缓冲区。接着,应用程序跟操作系统共享这个缓冲区。这样,操作系统内核和应用程序存储空间就不需要再进行任何的数据拷贝操作。
也就是说内存映射文件MMAP只有一次页缓存的复制,读时从磁盘文件复制到页缓存(page cache),写时从页缓存flush到磁盘文件,默认30s。MMAP与操作系统的Pagecache打交道。

普通文件IO需要复制两次,内存映射文件mmap复制一次,普通文件IO是堆内操作,内存映射文件是堆外操作

image.png
image.png

3、零拷贝实现方式之Sendfile

Sendfile 系统调用在Linux内核版本 2.1 中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。

Sendfile 系统调用的引入,不仅减少了数据复制,还减少了上下文切换的次数,大致如下图所示:

image.png

数据传送只发生在内核空间,所以减少了一次上下文切换;但是还是存在一次 Copy,能不能把这一次 Copy 也省略掉?

Linux2.4 内核中做了改进,将内核 buffer 中对应的数据描述信息(内存地址,偏移量)记录到相应的 Socket 缓冲区当中,这样连内核空间中的一次 CPU Copy 也省掉了,当DMA copy数据时,可以根据socket buffer中的内存地址和偏移量直接从kernel buffer中读取数据

image.png
image.png

sendfile()系统调用利用DMA引擎将文件中的数据拷贝到操作系统内核缓冲区中,接下来,DMA引擎将数据从内核socket缓冲区中拷贝到协议引擎

sendfile()系统调用不需要将数据拷贝或映射到应用程序地址空间,所以sendfile()只适用于应用程序地址空间不需要对所访问数据进行处理的情况。比如apache、nginx等web服务器使用sendfile传输静态文件

4、Kafka中的零拷贝

Kafka中的零拷贝主要体现在一下两个方面:

  • 生产者发送消息,并写入kafka broker节点的过程中,采用mmap文件映射的方式,DMA将网卡中的数据映射到kernel buffer中(即写入pagecache),然后再由系统写入磁盘。
    是通过MappedByteBuffer类实现的
  • 消费者从kafka broker读取数据时,采用的是sendfile方式,DMA将磁盘文件读到内核buffer之后,直接转到socket buffer进行网络发送。

Kafka速度的秘诀在于,它把所有的消息都变成一个的文件。通过mmap提高I/O速度,写入数据的时候它是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出

5、Netty中的零拷贝

Kafka中的零拷贝主要体现在一下三个方面:

  • Direct Buffers
    Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后再由直接内存拷贝到网卡接口层(Socket)。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。——类似于Sendfile方式

  • Composite Buffers
    传统的ByteBuffer,如果需要将两个ByteBuffer中的数据组合到一起,我们需要首先创建一个size=size1+size2大小的新的数组,然后将两个数组中的数据拷贝到新的数组中。但是使用Netty提供的组合ByteBuf,就可以避免这样的操作,因为CompositeByteBuf并没有真正将多个Buffer组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。

  • FileChannel.transferTo
    Netty中使用了java NIO FileChannel的transferTo方法,该方法依赖于操作系统实现零拷贝,它可以直接将文件缓冲区的数据发送到目标Channel(Sendfile方式),避免了传统通过循环write方式导致的内存拷贝问题。
image.png

6、java NIO中的零拷贝——transferTo

transferTo()的实现方式就是通过系统调用sendfile(),如下图数据流向

image.png
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容