Kafka的消息是保存或缓存在磁盘上的,一般认为在磁盘上读写数据是会降低性能的,因为寻址会比较消耗时间,但实际上,Kafka的特性之一就是高吞吐率。下面从数据写入和读取两方面分析,为什么Kafka速度会这么快。
数据写入
Kafka会把收到的消息都写入到硬盘中,它绝对不会丢失数据。为了优化写入速度,Kafka采用了两个技术,顺序写入和MMFile(Memory Mapped File)
顺序写入
磁盘读写的快慢取决于你怎么使用它,也就是顺序读写或者随机读写。再顺序读写的情况下,磁盘的顺序读写速度和内存持平。因为硬盘是机械结构,每次读写都会寻址->写入,其中寻址是一个机械动作,它是最消耗时间的。所以硬盘最讨厌随机I/O,最喜欢顺序I/O。为了提高读写硬盘的速度,Kafka就是使用顺序I/O。如果再内存做这些操作的时候,一个是Java对象的内存开销很大,另一个是随着堆内存数据的增多,Java的GC时间会变的很长。
使用磁盘操作有以下几个好处:
1)磁盘顺序读写速度超过内存随机读写。
2)JVM的GC效率低,内存占用大。使用磁盘可以避免这个问题。
3)系统冷启动后,磁盘缓存依然可用。
Memory Mapped Files
即便是顺序写入磁盘,磁盘的访问速度还是不可能追上内存。所以Kafka的数据并不是实时的写入磁盘,它充分利用了现代操作系统分页存储来利用内存提高I/O效率。
Memory Mapped Files也被翻译成内存映射文件,在64位操作系统中一般可以表示20G的数据文件,它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到磁盘上。通过mmap,进程像读写磁盘一样读写内存(当然是虚拟内存),也不必关心内存的大小,有虚拟内存兜底。使用这种方式可以获取很大的I/O提升,省去用户空间到内核空间复制的开销。但是也有一个很明显的缺陷:不可靠,写到mmap中的数据并没有真正的写到磁盘上,操作系统会在程序主动调用Flush的时候才把数据真正的写到磁盘。
Kafka提供了一个参数producer.type来控制是不是主动Flush:
如果Kafak写入到mmap之后就立即Flush, 然后再返回Producer叫同步(Sync)
如果kafka写入mmap之后立即饭后Producer不调用Flush叫异步(Async)
数据读取
Kafka在读取磁盘时作了哪些优化?
基于Sendfile实现Zero Copy
传统模式下,当需要对一个文件进行传输的时候,其具体流程细节如下:
1)调用Read函数,文件数据被Copy到内核缓冲区。
2)Read函数返回,文件数据从内核缓冲区Copy到用户缓冲区。
3)Write函数调用,将文件数据从用户缓冲区Copy到内核与Socket相关的缓冲区。
4)数据从Socket缓冲区Copy到相关协议引擎。
以上细节是传统Read/Write方式进行网络文件传输的方式,我们可以看到,在这个过程当中,文件数据实际上是经过了四次Copy操作,而Sendfile系统调用提供了一种减少以上多次Copy,提升文件传输性能的方法。
Sendfile的引入不仅减少了数据复制,还减少了上下文切换。
sendfile(socket,file,len);
运行流程如下:
1)Sendfile系统调用,文件数据被Copy至内核缓冲区。
2)再从内核缓冲区Copy至内核中Socket相关的缓冲区。
3)最后再Socket相关的缓冲区Copy到协议引擎。
再内核版本2.4之后,文件描述符结果被改变,Sendfile实现了更加简单的方式,再次减少一次Copy操作。
Kafka把所有的消息都存放在一个一个的文件中,当消费者需要数据的时候,Kafka直接把文件发送给消费者,配合mmap作为文件读写方式,直接把它传给Sendfile。
批量压缩
在很多情况下,系统的瓶颈不是CPU或磁盘,而是网络IO,对于需要在广域网上的数据中心之间发送消息的数据流水线尤其如此。进行数据压缩会消耗少量的CPU资源,不过对于Kafka而言,网络IO更应该考虑:
1)因为每个消息都压缩,但是压缩率相对很低,所以Kafka使用了批量压缩,即将多个消息一起压缩而不是单个消息压缩。
2)Kafka允许使用递归的消息集合,批量的消息可以通过压缩的形式传输并且在日志中也可以保持压缩格式,直到被消费者解压缩。
3)Kafka支持多种压缩协议,包括Gzip和Snappy压缩协议。
总结:Kafka速度的秘诀在于,它把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络IO损耗,通过mmap提高I/O速度。