Press "Enter" to skip to content

【数据挖掘】kafka的高性能真“香”

kafka相比redis,memcache等内存消息队列,可以将message写入速度慢容量大的硬盘充当存储层,关键是kafka数据持久化到磁盘的同时速度极快。当了解了kafka背后的诸多针对高性能的设计之后,可真香!当然kafka架构上的分布式partition存储,ISR数据同步等等都是协同配合凸显其高性能。但是这里介绍几个利用到操作系统特性的高性能设计,可以学习借鉴。

 

01

 

顺序读写sequence IO

 

kafka针对每一个消息存储单元partition,将来自producer的数据,顺序追加到该分区partition的segment文件中,以此实现顺序写。顺序写相比随机写可以减少磁盘寻道的开销,同时追加写入的速度与磁盘文件大小无关。

 

consumer从partition读取数据时,通过自带的偏移量offset从上次的位置开始读,以此实现顺序读。

 

关于磁盘读写的性能,kafka官方给出的数据是顺序IO:600M/s,随机IO:100k/s,下面1是ACM Queue给出的性能对比图:

 

 

图1:性能对比图

 

顺序读写,是kafka利用磁盘特性的一个重要体现。

 

02

 

页缓存page cache

 

在操作系统内核读取文件时需要注意两点:磁盘读写比内存慢很多,其中寻道比较耗时;物理内存需要加载文件后提供不同进程共享。page cache的引入就同时解决了以上两点,page cache是基于操作系统内存的缓存,是os自身管理的缓存。

 

在producer发送消息写入到broker时,并不会直接写入到磁盘,而是首先写入到page cache中,如图2,相当于存储到内存中,同时标记page属性为dirty,读操作会优先在page cache中查找,如果缺页会进行磁盘调度,然后返回数据。这里page cache将空闲内存当做磁盘缓存的设计,如果producer和consumer速度相当的话,相当于读写数据都是基于内存就完成了操作,直接绕过了磁盘的读写过程。而page cache相比jvm堆内存不会有gc的负担,同时对缓存的利用率两倍甚至更高,因为基本存储单元是紧凑的字节,而不是jvm中的object对象。操作系统层面对page cache的优化包括read-ahead,write-behind以及flush等机制。

 

 

图2:page cache缓存图

 

操作系统的可靠性保证了page cache的可靠性,如果机器断电或者os无法工作,kafka层面也有replication机制进行备份。当然操作系统也会控制page cache数据是否flush落盘到磁盘,操作系统的内核后台线程会定时检查是否需要flush数据,超过阈值时间会进行同步数据的操作。kafka提供的相关参数为flush.messages和flush.ms,不过不推荐使用。

 

03

 

零拷贝zero-copy

 

kafka能够在消费者在不断消费数据的同时并不会发生很高的磁盘io除了因为page cache这种系统缓存之外,零拷贝的应用也是功不可没,当kafka客户端从服务器读取数据时,如果不使用零拷贝技术,那幺会经历图3这样的过程:

 

 

图3:传统IO图

 

①操作系统从磁盘DMA拷贝数据到内核空间的读缓冲;

 

②应用程序从读缓冲CPU拷贝数据到用户空间的缓冲;

 

③应用程序从用户空间缓冲CPU拷贝数据到内核空间的socket缓冲;

 

④操作系统从socket缓冲DMA拷贝数据到NIC缓冲,通过网络发送到客户端。

 

可以看到两次的CPU拷贝其实是在重复的复制数据,Linux内核通过sendfile系统调用,实现了零拷贝。如下图4所示,磁盘数据通过DMA拷贝到内核态缓冲之后,直接拷贝到NIC缓冲发送。整个过程只有两次上下文切换,读磁盘,网络发送只需要调用一个sendfile方法。

 

 

图4:零拷贝图

 

再回到kafka当中,如果没有零拷贝的优化,kafka消费数据需要经过下图5的过程。借助零拷贝之后,不需要拷贝数据到应用进程缓冲,直接通过page cache拷贝数据的描述符到socket缓冲,然后数据直接拷贝到网卡缓冲上发送给消费端。kafka中通过java NIO中的transferTo和transferFrom底层调用操作系统的sendfile。

 

 

图5:无零拷贝优化的kafka图

 

04

 

内存文件映射mmap

 

Memory Mapped Files简称mmap,它的工作原理是将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数,在这里是直接利用操作系统的页来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到磁盘。通过mmap,进程像读写磁盘一样读写内存,直接将日志文件映射到虚拟地址空间,也不必关心内存的大小有虚拟内存为我们兜底。

 

为了提升mmap在内存中的可靠性,在必要时真正将数据写到硬盘。Kafka的参数producer.type设置是否主动flush。Kafka写入mmap之后也提供了异步线程不用立即flush刷盘返回到producer的机制。java中使用mappedByteButter封装了mmap。同时结合分区分段+索引的存储方式,显着提升了数据读取的效率和并行度。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注