消息中间件——Kafka—持久化、高性能(四)

储存模型

Kafka节点上,一个Partition的每个副本对应一个磁盘目录,新的日志,都是直接append到文件末尾,所以不管文件多大,写入总是O(1)的时间复杂度。但如果文件很大,顺序查找的效率也会很低。kafka通过两种方式解决:分段、索引。

分段

比如有100条 Message,它们的offset是从0到99。假设将数据文件分成5段,第一段为0-19,第二段为20-39,以此类推,每段放在一个单独的数据文 件里面,数据文件以该段中最小的offset命名。这样在查找指定offset的Message的时候,用二分查找就可以定位到该Message在哪个段 中。

索引

数据文件分段使得可以在一个较小的数据文件中查找对应offset的 Message了,但是这依然需要顺序扫描才能找到对应offset的Message。为了进一步提高查找的效率,Kafka为每个分段后的数据文件建立 了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为.index。

索引文件中包含若干个索引条目,每个条目表示数据文件中一条Message的索引。索引包含两个部分(均为4个字节的数字),分别为相对offset和position。

注: index文件中并没有为数据文件中的每条Message建立索引,而是采用了 稀疏存储 的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。但缺点是没有建立索引的 Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。

高性能

顺序读写

kafka采用的磁盘的顺序读写比无序快了太多,这是由操作系统决定的,即使是普通的机械磁盘,顺序访问速率也接近了内存的随机访问速率。

即使是顺序读写,过于频繁的大量小IO操作一样会造成磁盘的瓶颈,此时又变成了随机读写。Kafka的策略是把消息集合在一起,批量发送,尽可能减少对磁盘的访问。所以,Kafka的Topic和Partition数量不宜过多,超过64个Topic/Partition以后,Kafka性能会急剧下降。

零拷贝

Kafka中存在大量的网络数据持久化到磁盘(Producer到Broker)和磁盘文件通过网络发送(Broker到Consumer)的过程。这一过程的性能直接影响Kafka的整体吞吐量。

Linux 2.4+内核通过sendfile系统调用,提供了零拷贝。数据通过DMA拷贝到内核态Buffer后,直接通过DMA拷贝到NIC Buffer,无需CPU拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件-网络发送由一个sendfile调用完成,整个过程只有两次上下文切换,因此大大提高了性能。

Kafka的数据传输通过TransportLayer来完成,其子类PlaintextTransportLayer通过Java NIO的FileChannel的transferTo和transferFrom方法实现零拷贝。

页缓存

Kafka并不太依赖JVM内存大小,而是主要利用Page Cache,如果使用应用层缓存(JVM堆内存),会增加GC负担,增加停顿时间和延迟,创建对象的开销也会比较高。

读取操作可以直接在Page Cache上进行,如果消费和生产速度相当,甚至不需要通过物理磁盘直接交换数据,这是Kafka高吞吐量的一个重要原因。

这么做还有一个优势,如果Kafka重启,JVM内的Cache会失效,Page Cache依然可用。

文章目录
  1. 1. 储存模型
    1. 1.1. 分段
    2. 1.2. 索引
  2. 2. 高性能
    1. 2.1. 顺序读写
    2. 2.2. 零拷贝
    3. 2.3. 页缓存
|