「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

提到武侠小说,自然就会想到古龙和金庸。在古龙的武侠世界里,从不缺乏盖世豪气和磅礴场面。让我印象最深的就是浪子,通常浪子武功都很厉害,如何厉害,那就是快、更快,还没看到兵刃离鞘,敌手己毙。

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

来自网络

Kafka在消息中间件领域,以快闻名(当然不仅仅是),正所谓,天下武功,唯快不破。在咱们计算机领域,也是一样。希望通过此文,和大家一起来看看Kafka兵刃是怎么离鞘的。

理论上,如果消费者拉取消息的速度,和发布者发布消息的速度一致,消息将不会落到硬盘上,这样是最高效的情况。但大部分情况下,有很高的概率,消息是的发布和消费是很难平衡的,如果发布者和消费者分越多,出现这种读写文件的情况就会越多。

文件

也就是我们打开的文本,这里可以具体联想到Kafka所写入的消息文件。存储在硬盘上,通常一个文件存储在硬盘上时,都是无序的,如下图所示:

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

通常从硬盘读取文件时,会放到Buffer Cache中,会以1KB为单位,是块设备的缓冲区,如上图的Disk。那为什么下面的文件为以4KB为单位呢,因为还有一个cache叫Page Cache,每个Page Cache通常是4KB或6KB,这里以4KB为例,这个纯粹是从软件逻辑层面给取的一个名字。下图展示出Page Cache和Buffer Cache这间的关系:

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

可以看出Page Cache映射出了文件的逻辑状态。再来看一个真实的情况:

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

通过free -m命令可以查看缓存的使用量,如上图所示总物理内存是2G(total):

  • buffers: Buffer Cache的内存,块设备的读写缓冲区,更靠近存储设备,或直接就是disk的缓冲区。上图显示系统分使用了155M
  • cached: Page Cache的内存, 文件系统的cache,是memory的缓冲区。上图显示系统已使用了 686M
  • -/+ buffers/cache,这一行显示的是 1887 - (155 + 686) = 1045,表示除了buffer cache和page cache其它已经使用的量,如应用程序使用的内存等。113 + (155 + 686) = 954,表示可用的内存还有954M,也就是buffer cache和page cache并不是一直被占用的,有可能被回收给其它更需要内存的情况使用。

可以看出物理内存是很宝贵的,使用时需要精打细算。不管摩尔定律怎么说,我们都需要用高效的方法从众多的Page Cache中查询自己所需的内容是否已经在Page Cache里了,如何快速查询?这里用到了radix tree:

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

来自网络

可以很清楚的看到查找路径。如果不在Page Cache里,就需要走一遍较为费时的硬盘文件读取了。为了提高命中率,感兴趣的朋友可以google一下Page Cahce的预读和替换机制。

读(read) & 写(write)

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

普通读写流程如上图所示,步骤分解:

  1. 应用程序会分配(alloc)自己的应用内存,用来存储自己需要文件信息,并发起读请求
  2. VFS也就是虚拟文件系统会调用系统读操,找到对应的文件并放到内核内存中
  3. DMA copy,直接内存访问的好处是释放了CPU,可以由直接操作内核内存,将文件加载到Page Cache中
  4. CPU copy,注意到这里需要从左边的内核空间(Kernel Space),通过CPU,拷贝到用户空间(User Space),大家也喜欢叫这两个空间分别为内核态和用户态,不管怎么叫,重点是当跨越状态的操作发生时,效率会受到较到的影响,同时让同一份内容,在内存中有多处拷贝,这是一种典型的浪费。
  5. 上述应用程序需要将读取的文件,通过网络发送出去,传递给服务器进程。这里发出写请求。
  6. 应用内存又一次以CPU copy的形式将同一份内容拷贝到套接字缓存(Socket Buffer)。到这里实际上已经出现了三份内存冗余。
  7. 从套接字缓存到网卡缓存(NIC buffer),使用的是DMA copy,相对帮了CPU的忙。最后发送给了服务器进程。

可以看出,步骤4和步骤6是需要得点优化的地方,因为不仅有从用户空间到内核空间的切换,还有CPU时间的占用,那Kafka是如何优化的呢?

读(read) VS 内存映射(mmap)

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

<code>file_buf = mmap(file, len);/<code>

可以看出使用了mmap带来的改变主要是4和6:

  • 步骤4:应用缓存和内核缓存共享,这样就减少了一次cup copy,同时相对降低了从内核空间到用户空间的切换成本
  • 步骤6:相比较之前,仍然用的是CPU copy,但不同的是都将发生在内核空间,减少了空间的切换

总体来说减少了一次数据内存的冗余,和近两次空间切换。至于关心mmap是如何完成这个魔术的同学,也可以进一步google,也将会是一个有趣的过程。

继续优化。

写(write) VS 文件发送(sendfile)

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

  • 步骤5:sendfile系统调用,利用DMA引擎,将文件内容拷贝到内核缓冲区去
  • 步骤6:不同于之前的CPU copy,这里仅将带有文件位置和长度信息的缓冲区描述符添加到 socket缓冲区中去,而不是将数据从操作系统内核缓冲区拷贝到socket缓冲区中
  • 步骤7:DMA引擎会将数据直接从内核缓冲区拷贝到协议引擎(本例为网卡)中去,这样又减少了最后一次数据拷贝

总体来说,直接利用DMA引擎的优势,将内容直接拷贝到内核缓冲区,应用程序缓冲区没有冗余。同时将有限的信息传给socket缓冲区,最后直接从内核缓冲区拷贝到网卡缓冲区。

全景图:内存映射(mmap) & 文件发送(sendfile)

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化

可以看出,结合了mmap和sendfile的读写操作,文件缓存只有一份,存在于内核缓冲区中。从而达到了零拷贝的要求。

Kafka在读写方面做了很多设计和优化,这篇是从Linux操作系统的角度解释了Kafka的用心之处。希望对大家了解Kafka兵刃是如何离鞘的高深武学造诣,有一定的帮助。也欢迎广大武学爱好者前来共同探讨学习。

全文完

「Linux」零拷贝 - 对Kafka文件系统读写操作的影响和优化


分享到:


相關文章: