kafka 进阶
高性能架构
Kafka的高性能在宏观架构层面和具体实现层面都有体现。从宏观架构层面来讲,主要分两点:
- 利用Partition实现并行处理 由于不同Partition可位于不同机器,因此可以充分利用集群优势,实现机器间的并行处理。另一方面,由于Partition在物理上对应一个文件夹,即使多个Partition位于同一个节点,也可通过配置让同一节点上的不同Partition置于不同的磁盘上,从而实现磁盘间的并行处理,充分发挥多磁盘的优势。
- ISR实现可用性与数据一致性的动态平衡 Kafka的数据复制是以Partition为单位的。而多个备份间的数据复制,通过Follower向Leader拉取数据完成。从一这点来讲,Kafka的数据复制方案接近于Master-Slave方案。不同的是,Kafka既不是完全的同步复制,也不是完全的异步复制,而是基于ISR的动态复制方案
高效使用磁盘、零拷贝、减少网络开销。
顺序写磁盘
Kafka的整个设计中,Partition相当于一个非常长的数组,而Broker接收到的所有消息顺序写入这个大数组中。同时Consumer通过Offset顺序消费这些数据,并且不删除已经消费的数据,从而避免了随机写磁盘的过程。 由于磁盘有限,不可能保存所有数据,实际上作为消息系统Kafka也没必要保存所有数据,需要删除旧的数据。而这个删除过程,并非通过使用“读-写”模式去修改文件,而是将Partition分为多个Segment,每个Segment对应一个物理文件,通过删除整个文件的方式(直接删除Segment对应的整个log文件和整个index文件而非删除文件中的部分内容)去删除Partition内的数据。这种方式清除旧数据的方 式,也避免了对文件 的随机写操作。
Page Cache
首先介绍下Page cache机制:page cache是Linux操作系统的一个特色,系统内核为文件系统文件设置了一个缓存,对文件读写的数据内容都缓存在这里。这个缓存称为page cache(页缓存)。page cache是通过将磁盘中的数据缓存到内存中,从而减少磁盘I/O操作,从而提高性能。此外,还要确保在page cache中的数据更改时能够被同步到磁盘上。 Page cache由内存中的物理page组成,其内容对应磁盘上的block。page cache的大小是动态变化的,可以扩大,也可以在内存不足时缩小。cache缓存的存储设备被称为后备存储(backing store)。 当内核发起一个读请求时,首先会检查请求的数据是否缓存到了page cache中,如果有,那么直接从内存中读取,不需要访问磁盘。若未命中内存,就必须从磁盘中读取数据,然后内核将读取的数据缓存到cache中。 当内核发起一个写请求时,同样是直接往cache中写入,后备存储中的内容不会直接更新。内核会将被写入的page标记为dirty,并将其加入dirty list中。内核会周期性地将dirty list中的page写回到磁盘上,从而使磁盘上的数据和内存中缓存的数据一致。 Cache的回收有LRU和Two-List策略,Linux使用的是后者
使用Page Cache的好处如下: 1)I/O Scheduler会将连续的小块写组装成大块的物理写从而提高性能; 2)I/O Scheduler会尝试将一些写操作重新按顺序排好,从而减少磁盘头的移动时间; 3)充分利用所有空闲内存(非JVM内存)。如果使用应用层Cache(即JVM堆内存),会增加GC负担; 4)读操作可直接在Page Cache内进行。如果消费和生产速度相当,甚至不需要通过物理磁盘(直接通过Page Cache)交换数据; 5)如果进程重启,JVM内的Cache会失效,但Page Cache仍然可用。 Broker收到数据后,写磁盘时只是将数据写入Page Cache,并不保证数据一定完全写入磁盘。从这一点看,可能会造成机器宕机时,Page Cache内的数据未写入磁盘从而造成数据丢失。但是这种丢失只发生在机器断电等造成操作系统不工作的场景,而这种场景完全可以由Kafka层面的Replication机制去解决。如果为了保证这种情况下数据不丢失而强制将Page Cache中的数据Flush到磁盘,反而会降低性能。也正因如此,Kafka虽然提供了flush.messages和flush.ms两个参数将Page Cache中的数据强制Flush到磁盘,但是Kafka并不建议使用。
支持多Disk Drive
Broker的log.dirs配置项,允许配置多个文件夹。如果机器上有多个Disk Drive,可将不同的Disk挂载到不同的目录,然后将这些目录都配置到log.dirs里。Kafka会尽可能将不同的Partition分配到不同的目录,也即不同的Disk上,从而充分利用了多Disk的优势。 在$KAFKA_HOME/config/server.properties文件中,可以配置多个Kafka的日志目录:
字节零拷贝
为了减少字节拷贝,采用了sendfile系统调用,实现了零拷贝。Kafka中存在大量的网络数据持久化到磁盘(Producer到Broker)和磁盘文件通过网络发送(Broker到Consumer)的过程。这一过程的性能直接影响Kafka的整体吞吐量。 以将磁 盘文件通过 网络发送为 例。传统模 式下的方法 是先将文件 数据读入内 存,然后通 过Socket将 内存中的数 据发送出去。这一过程实际上发生了四次数据拷贝。首先通过系统调用将文件数据读入到内核态Buffer(DMA拷贝),然后应用程序将内存态Buffer数据读入到用户态Buffer(CPU拷贝),接着用户程序通过Socket发送数据时将用户态Buffer数据拷贝到内核态Buffer(CPU拷贝),最后通过DMA拷贝将数据拷贝到NIC Buffer。同时,还伴随着四次上下文切换。
Linux 2.4+内核通过sendfile系统调用,提供了零拷贝。数据通过DMA拷贝到内核态Buffer后,直接通过DMA拷贝到NIC Buffer,无需CPU拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件-网络发送由一个sendfile调用完成,整个过程只有两次上下文切换,因此大大提高了性能
实际上是否能使用零拷贝与操作系统相关,如果操作系统提供sendfile这样的零拷贝系统调用,则Kafka会通过系统调用充分利用零拷贝的优势,否则并不能实现零拷贝。