1.磁盘存储
Kafka依赖于文件系统(更底层来说就是磁盘)来存储和缓存消息。在我们的印象中,对于各个存储介质的速度认知大体上层级越高代表速度越快,很显然,磁盘处于一个比较尴尬的位置,这不禁让我们怀疑Kafka采用这种持久化形式能否提供有竞争力的性能。然而,事实上,磁盘可以比我们想象的要快。
操作系统可以针对线性读写做深层次的优化,比如预读(read-ahead,提前将一个比较大的磁盘块读入内存)和后写(write-behind,将很多小的逻辑写操作合并起来组成一个大的物理写的操作)技术,顺序写盘的速度不仅比随机写盘的速度快,而且也比随机写内存的速度快。
Kafka在设计时采用了文件追加的方式来写入消息,即只能在日志文件的尾部追加新的消息,并且也不允许修改已写入的消息,这种方式属于典型的顺序写盘的操作,所以就算Kafka使用磁盘作为存储介质,它所能承载的吞吐量也不容小觑。但这并不是让Kafka在性能上具备足够竞争力的唯一因素。
2、页缓存:用来减少对磁盘I/O的操作。
在 Kafka 中,它会利用 PageCache 加速消息读写。PageCache 是现代操作系统都具有的一项基本特性。通俗地说,PageCache 就是操作系统在内存中给磁盘上的文件建立的缓存。无论我们使用什么语言编写的程序,在调用系统的 API 读写文件的时候,并不会直接去读写磁盘上的文件,应用程序实际操作的都是 PageCache,也就是文件在内存中缓存的副本。应用程序在写入文件的时候,操作系统会先把数据写入到内存中的 PageCache,然后再一批一批地写到磁盘上。读取文件的时候,也是从 PageCache 中来读取数据,这时候会出现两种可能情况。一种是 PageCache 中有数据,那就直接读取,这样就节省了从磁盘上读取数据的时间;另一种情况是,PageCache 中没有数据,这时候操作系统会引发一个缺页中断,应用程序的读取线程会被阻塞,操作系统把数据从文件中复制到 PageCache 中,然后应用程序再从 PageCache 中继续把数据读出来,这时会真正读一次磁盘上的文件,这个读的过程就会比较慢。用户的应用程序在使用完某块 PageCache 后,操作系统并不会立刻就清除这个 PageCache,而是尽可能地利用空闲的物理内存保存这些 PageCache,除非系统内存不够用,操作系统才会清理掉一部分 PageCache。清理的策略一般是 LRU 或它的变种算法,这个算法我们不展开讲,它保留 PageCache 的逻辑是:优先保留最近一段时间最常使用的那些 PageCache。Kafka 在读写消息文件的时候,充分利用了 PageCache 的特性。一般来说,消息刚刚写入到服务端就会被消费,按照 LRU 的“优先清除最近最少使用的页”这种策略,读取的时候,对于这种刚刚写入的 PageCache,命中的几率会非常高。也就是说,大部分情况下,消费读消息都会命中 PageCache,带来的好处有两个:一个是读取的速度会非常快,另外一个是,给写入消息让出磁盘的 IO 资源,间接也提升了写入的性能。
3、零拷贝:
所谓的零拷贝就是指将数据直接从磁盘文件复制到网卡设备中,而不需要经由应用程序之手,零拷贝大大提高了应用程序的性能,减少了内核和用户模式之间的上下文切换,对于Linux操作系统来说,零拷贝技术依赖于底层的sendfile()方法实现。对应于java语言,FileChannal.transferTo()方法的底层实现就是sendfile()方法。;零拷贝技术通过DMA技术将文件内容复制到内核模式下的ReadBuffer中。不过没有数据被复制到SocketBuffer。DMA引擎直接将数据从内核模式中传递到网卡设备。 零拷贝是针对内核模式而言的,数据在内核模式下实现了零拷贝。