面试问:Kafka为何速度那么快?该怎么回答

Kafka的消息是保存或缓存在磁盘上的,通常认为在磁盘上读写数据是会下降性能的,由于寻址会比较消耗时间,可是实际上,Kafka的特性之一就是高吞吐率。即便是普通的服务器,Kafka也能够轻松支持每秒百万级的写入请求,超过了大部分的消息中间件,这种特性也使得Kafka在日志处理等海量数据场景普遍应用。java

针对Kafka的基准测试能够参考,Apache Kafka基准测试:每秒写入2百万(在三台廉价机器上)web

下面从数据写入和读取两方面分析,为何Kafka速度这么快面试

1、写入数据

Kafka会把收到的消息都写入到硬盘中,它绝对不会丢失数据。为了优化写入速度Kafka采用了两个技术, 顺序写入和MMFile 。后端

顺序写入

磁盘读写的快慢取决于你怎么使用它,也就是顺序读写或者随机读写。在顺序读写的状况下,磁盘的顺序读写速度和内存持平。缓存

由于硬盘是机械结构,每次读写都会寻址->写入,其中寻址是一个“机械动做”,它是最耗时的。因此硬盘最讨厌随机I/O,最喜欢顺序I/O。为了提升读写硬盘的速度,Kafka就是使用顺序I/O。服务器

并且Linux对于磁盘的读写优化也比较多,包括read-ahead和write-behind,磁盘缓存等。若是在内存作这些操做的时候,一个是JAVA对象的内存开销很大,另外一个是随着堆内存数据的增多,JAVA的GC时间会变得很长,使用磁盘操做有如下几个好处:微信

一、顺序写入磁盘顺序读写速度超过内存随机读写网络

二、顺序写入JVM的GC效率低,内存占用大。使用磁盘能够避免这一问题app

三、顺序写入系统冷启动后,磁盘缓存依然可用异步

下图就展现了Kafka是如何写入数据的, 每个Partition其实都是一个文件 ,收到消息后Kafka会把数据插入到文件末尾(虚框部分):

这种方法有一个缺陷——没有办法删除数据 ,因此Kafka是不会删除数据的,它会把全部的数据都保留下来,每一个消费者(Consumer)对每一个Topic都有一个offset用来表示读取到了第几条数据 。

两个消费者:

一、顺序写入Consumer1有两个offset分别对应Partition0、Partition1(假设每个Topic一个Partition);

二、顺序写入Consumer2有一个offset对应Partition2。 这个offset是由客户端SDK负责保存的,Kafka的Broker彻底无视这个东西的存在; 通常状况下SDK会把它保存到Zookeeper里面,因此须要给Consumer提供zookeeper的地址。

若是不删除硬盘确定会被撑满,因此Kakfa提供了两种策略来删除数据:

一、顺序写入一是基于时间。

二、顺序写入二是基于partition文件大小。

Memory Mapped Files

即使是顺序写入硬盘,硬盘的访问速度仍是不可能追上内存。因此Kafka的数据并非实时的写入硬盘 ,它充分利用了现代操做系统分页存储来利用内存提升I/O效率。

Memory Mapped Files(后面简称mmap)也被翻译成 内存映射文件 ,在64位操做系统中通常能够表示20G的数据文件,它的工做原理是直接利用操做系统的Page来实现文件到物理内存的直接映射。

完成映射以后你对物理内存的操做会被同步到硬盘上(操做系统在适当的时候)。

经过mmap,进程像读写硬盘同样读写内存(固然是虚拟机内存),也没必要关心内存的大小有虚拟内存为咱们兜底。

使用这种方式能够获取很大的I/O提高,省去了用户空间到内核空间复制的开销(调用文件的read会把数据先放到内核空间的内存中,而后再复制到用户空间的内存中。) 但也有一个很明显的缺陷——不可靠,写到mmap中的数据并无被真正的写到硬盘,操做系统会在程序主动调用flush的时候才把数据真正的写到硬盘。

Kafka提供了一个参数——producer.type来控制是否是主动flush,若是Kafka写入到mmap以后就当即flush而后再返回Producer叫 同步 (sync);写入mmap以后当即返回Producer不调用flush叫异步 (async)。

2、读取数据

Kafka在读取磁盘时作了哪些优化?

基于sendfile实现Zero Copy

传统模式下,当须要对一个文件进行传输的时候,其具体流程细节以下:

一、基于sendfile实现Zero Copy调用read函数,文件数据被copy到内核缓冲区

二、read函数返回,文件数据从内核缓冲区copy到用户缓冲区

三、write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。

四、数据从socket缓冲区copy到相关协议引擎。

以上细节是传统read/write方式进行网络文件传输的方式,咱们能够看到,在这个过程中,文件数据其实是通过了四次copy操做:

硬盘—>内核buf—>用户buf—>socket相关缓冲区—>协议引擎

而sendfile系统调用则提供了一种减小以上屡次copy,提高文件传输性能的方法。

在内核版本2.1中,引入了sendfile系统调用,以简化网络上和两个本地文件之间的数据传输。sendfile的引入不只减小了数据复制,还减小了上下文切换。 sendfile(socket, file, len);

运行流程以下:

一、sendfile系统调用,文件数据被copy至内核缓冲区

二、再从内核缓冲区copy至内核中socket相关的缓冲区

三、最后再socket相关的缓冲区copy到协议引擎 相较传统read/write方式,2.1版本内核引进的sendfile已经减小了内核缓冲区到user缓冲区,再由user缓冲区到socket相关缓冲区的文件copy,而在内核版本2.4以后,文件描述符结果被改变,sendfile实现了更简单的方式,再次减小了一次copy操做。

在Apache、Nginx、lighttpd等web服务器当中,都有一项sendfile相关的配置,使用sendfile能够大幅提高文件传输性能。

Kafka把全部的消息都存放在一个一个的文件中,当消费者须要数据的时候Kafka直接把文件发送给消费者,配合mmap做为文件读写方式,直接把它传给sendfile。

批量压缩

在不少状况下,系统的瓶颈不是CPU或磁盘,而是网络IO,对于须要在广域网上的数据中心之间发送消息的数据流水线尤为如此。进行数据压缩会消耗少许的CPU资源,不过对于kafka而言,网络IO更应该须要考虑。

一、若是每一个消息都压缩,可是压缩率相对很低,因此Kafka使用了批量压缩,即将多个消息一块儿压缩而不是单个消息压缩

二、Kafka容许使用递归的消息集合,批量的消息能够经过压缩的形式传输而且在日志中也能够保持压缩格式,直到被消费者解压缩

三、Kafka支持多种压缩协议,包括Gzip和Snappy压缩协议

3、总结

Kafka速度的秘诀在于,它把全部的消息都变成一个批量的文件,而且进行合理的批量压缩,减小网络IO损耗,经过mmap提升I/O速度,写入数据的时候因为单个Partion是末尾添加因此速度最优;读取数据的时候配合sendfile直接暴力输出。

更多优质文章请关注个人微信公众号【java后端技术精选】,回复“1024”和“面试”能够领取优质的视频学习资源