Kafka——Kafka高吞吐高性能原理解析


Kafka作为一个高吞吐、高可用、可伸缩并能提供数据持久化的发布订阅系统。那么其高吞吐的特性使如何做到的呢?答案就是磁盘顺访问和zero-copy零拷贝。

Kafka架构图如下:
在这里插入图片描述

磁盘顺序访问

Kafka非常依赖于文件来存储和缓存消息,可是我们很多人会认为磁盘太慢了,但其实对于一个配置了6块7200rpm SATA RAID-5机械硬盘的JBOD,其线性写入速度可达600MB/s。甚至一些研究中发现在某些场景下,磁盘顺序访问比内存随机访问还要更快,下面是其中的测试结果对比图:
磁盘顺序访问
注意:这张图所在文章是2009年发布的,现在磁盘和内存的读写性能已经有了大幅的提升。

生产者在向Kafka写入消息的时候,Kafka并没有采用将数据累计写入到内存然后再刷到磁盘的方式,而是将数据立即写入文件系统的持久化日志中,而不必刷新到磁盘,其实也就是写入到内核的pagecache中。

内存数据的保存

为了弥补磁盘与内存之间的性能差距,现代操作系统越来越倾向于使用主内存来作为磁盘缓存。操作系统在回收内存的时候,可以以很小的性能损失将所有空闲缓存转移到磁盘缓存中。所有磁盘的读写都通过这个统一的缓存。

此外,由于Kafka是基于Java和Scala这种基于JVM的语言构建的,对JVM内存熟悉的人都知道,它存在两个缺点:

  1. 对象的内存开销非常高,甚至会使存储的数据大小翻倍。
  2. 随着堆内数据的增加,垃圾收集会变得越来越频繁、缓慢。

为了避免以上这些问题,Kafka使用pagecache而不是通过JVM进程来保存内存数据。除此之外,这样做还能最大化利用计算机中的可用内存,甚至在服务重启的时候也不受影响。

pagecache

在Linux系统中,被写入到文件系统中的数据在写入磁盘之前是被维护在pagecache中的,操作系统将主内中未使用的部分作为pagecache。数据的刷盘是由一组称为pdflush的后台线程完成的。

相比使用进程缓存来存储数据,使用pagecache有以下优势:

  • I/O调度器会将连续的小的写操作处理为大的物理写操作,从而提高吞吐量。
  • I/O调度器将尝试重新排列写操作,以最大限度地减少磁盘头的移动,从而提高吞吐量。
  • pagecache会自动使用所有可用的空闲内存。

零拷贝zero-copy

在介绍零拷贝(zero copy)之前,我们先来看一个场景,现在有很多Web应用程序会提供大量的静态内容,这些数据被发送到网络之前,会先通过系统内核将磁盘数据读出来,然后发送到用户侧的应用程序,之后应用程序将这些数据在发送到系统内核,最后系统内核将数据写入socket。

这个过程中,每次数据从用户到系统内核和从系统内核到用户,都必须进行复制和上下文切换,这将消耗CPU和内存带宽。为了避免不必要的数据拷贝和上下文切换,我们可以使用零拷贝技术,直接请求系统内核将数据直接从磁盘拷贝到socket,而不用经过用户应用程序。

Java类库通过java.nio.channels.filechannel中的transferTo()方法在Linux系统上支持零拷贝。

下面通过从文件中读取数据并通过网络将数据传输到另一个程序这样一个场景,来比较下传统的数据传输方式和零拷贝数据传输方式在实现原理上的差异,以及零拷贝的优化方式。

1. 传统的数据传输方式

在传统的传输方式中,从文件拷贝数据到网络,需要在用户态和内核态之间进行4次上下文切换,而且数据也被拷贝了4次。

下图展示了数据传输的整个过程:
在这里插入图片描述

对应的步骤:

  1. 第一步操作系统从磁盘文件中读取数据到内核缓存(pagecache),对应图中的Read buffer,会引发从用户态到内核态的上下文切换。第一次数据拷贝是通过DMA(direct memory access)引擎执行的。
  2. 应用程序将数据从Read buffer复制到用户缓存(对应图中Application buffer),此时读调用会返回,引发第二次上下文切换,从内核态切换到用户态。
  3. 应用程序将数据复制到内核空间的Socket buffer,此时会引发第三次上下文切换,从用户态切换到内核态。
  4. 操作系统将数据从Socket buffer复制到NIC buffer,在这里可通过网络发送数据。此时,调用返回会引发第四次上下文切换,从内核态切换到用户态。

2. 零拷贝数据传输方式

从上面传统数据传输方式的过程可以看出,其实第二步和第三步的数据拷贝完全是没有任何必要的,应用程序只是把数据缓存下来,有发送给了Socket。所以,为什么不能将数据从Read Buffer直接发送到Socket buffer中呢?其实,零拷贝技术正是这样做的!

零拷贝是需要底层操作系统支持的。在UNIX和Linux中,零拷贝是由sendfile()方法实现的,此方法将数据从一个文件描述符复制另一个文件描述符,这里的复制操作是由内核来完成的,所以sendfile()方法要比联合使用read()和write()方法高效,因为后者需要从用户空间读取/写入数据。

下图展示了零拷贝方式数据传输的整个过程:
在这里插入图片描述
应用程序中是transferTo()方法(FileChannel类中的方法)之后,上下文切换由原来的4次减少为2次,数据复制由原来的4次减少为3次。

但是,这还并没有达到零拷贝的目的,因为还是会有3次数据拷贝。为了避免内核中所有的数据复制,我们就需要网络接口支持收集操作,这就意味着等待传输的数据不需要在连续的内存中,可以分散在不同的内存空间。在Linux 内核2,.4及之后的版本中,scoket buffer descriptor是满足这个条件的,这也就真正做到了零拷贝zero-copy。
gather

Kafka就是利用磁盘顺序追加写、pagecache和零拷贝优化来实现高吞吐特性的。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值