0


揭秘 Kafka 高性能之谜:一文读懂背后的设计精粹与技术实现

    Kafka在性能方面有着显著的优势,这也使得Kafka的应用非常广泛,那kakfa的性能为何如此优异呢?本文将带你探寻kafka高性能之谜。

    kafka的高性能概括起来有如下几点:顺序写入磁盘与I/O优化、批量处理、页缓存、零拷贝技术、分区并行处理、高效的消息压缩。下面将分别极少这几方面的内容。

一、顺序写入磁盘与I/O优化

    人们对于磁盘速度慢的普遍印象,使得人们对于持久化的架构提供强有力的性能产生怀疑。事实上,磁盘的速度快慢取决于使用磁盘的方式,而且设计合理的磁盘结构通常可以和网络一样快。

    关于磁盘性能的关键事实是,磁盘的吞吐量和过去十年里磁盘的寻址不同。使用6个7200rpm、SATA接口、RAID-5的磁盘阵列在JBOD配置下的顺序写入的性能约为600MB/s,但随机写入的性能仅约为100k/s,相差6000倍以上。因为线性的读取和写入是磁盘使用模式中最有规律的,并且由操作系统进行大量的优化,现代操作系统提供了read-ahead和write-behind技术。

    read-ahead:操作系统文件预读,linux系统内核将指定文件的某区域预读进页缓存起来,便于对该区域进行读取时,不会因缺页而阻塞。预读可以有效地减少磁盘的寻址次数和应用程序的I/O等待时间。

    write-behind:将多个小型的逻辑合并成一次大型的物理磁盘写入。

1.1 数据在kafka中是如何存储的

    一个主题对应多个分区,每个分区又有多个副本,一个副本对应一个日志(Log),为了防止Log过大,Kafka又引入了日志分段(LogSegment),将log切分成多个LogSegmnt,相当于一个巨型文件被平均分成多个相对较小的文件,这样便于消息的维护和清理。

    事实上,Log和LogSegment也不是纯粹物理意义上的概念,Log在物理上只以文件夹的形式存储,而每个LogSegment对应于磁盘上一个日志文件和两个索引文件,以及可能的其他文件。下图描述了主题、分区与副本、日志之间的关系。![](https://img-blog.csdnimg.cn/direct/22e1c39cc4c74dbbbd656fab30278990.png)

    在Log中追加消息时是 **顺序写入 **的,只有最后一个LogSegment才能执行写入操作,在此之前所有的LogSegment都不能写入数据。日志中还存在索引文件,可以高效的定位消息,提高效率。

    从这里就能看出,日志的内容是顺序写入的,所以磁盘的性能比随机写入要好的多,这是kafka高性能的第一个原因。

二、批量处理

    批量处理提高性能很好理解,将多次操作合并为一次操作.

    生产者发送消息时支持批量发送消息,将多条消息积累到一个批次中,然后一次性发送给Kafka Broker,这样可以减少网络I/O次数,显著提升吞吐量,并降低延迟,尤其是在网络带宽成为瓶颈时。

    在消费者端,Kafka也支持批处理的机制。消费者可以在处理完一批消息后,一起提交这些消息的偏移量(Offset),而不是每次消费一条消息就立即提交Offset。这种批量提交策略有助于减少与Kafka Broker之间的通信开销,同时也能保证了整体消费进度的一致性。

    在broker端,可以通过write-behind等技术,将多次磁盘操作合并为一个,减少刷盘操作。

    这是Kafka高性能的第二个原因。

三、页缓存技术

    页缓存是操作系统实现的一种主要的磁盘缓存,以此用来减少对磁盘I/O的操作。具体来说就是把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问。

    当一个进程准备读取磁盘上的文件时,操作系统会先查看读取的数据所在的页(page)是否在页缓存(pagecache)中,如果存在则直接返回数据,从而避免了对物理磁盘的I/O操作;如果没有命中,则操作系统会像磁盘发起读取请求并将读取的数据页存入页缓存,之后再将数据返回给进程。同样一个进程需要将数据写入磁盘,那么操作系统也会检测数据对应的页是否在页缓存中,如果不存在则会先在页缓存中添加相应的页,最后将数据写入对应的页。被修改的页也就变成了脏页,操作系统会在合适的时间把脏页写入磁盘,以保持数据的一致性。

    Linux操作系统中vm.dirty_background_ratio参数用来指定当脏页数量达到系统内存的百分之多少后会触发pdflush/flush/kdmflush等后台回写进程的运行来处理脏页。

    kafka中大量的使用了页缓存,这也是kafka实现高吞吐量的重要因素之一。虽然消息都是先被写入页缓存,然后由操作系统负责具体的刷盘任务,但在kafka中同样提供了同步刷盘即阶段性强制刷盘(fsyc)的功能。

    这是Kafka高性能的第三个原因。但有一种情况需要引起注意,会导致页缓存失效。在kafka出现严重挤压时,生产者开始时的确是把消息写入到了页缓存,但由于消费者处理速度过慢,挤压严重,那页缓存中的数据会不断的被置换到磁盘上,当消费者消费消息时,可能需要从磁盘读取了,这对性能是极大的损耗,所以要尽量避免kafka出现严重的积压情况。

四、零拷贝技术

    磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存10倍以上,所以针对磁盘的优化技术非常多,比如零拷贝、直接I/O、异步I/O等等,这些优化的目的是为了提高系统的吞吐量,这里主要介绍零拷贝。

    我们以文件传输作为切入点,来分析I/O工作方式,以及如何优化传输文件的性能。

4.1 DMA 技术

    在没有DMA技术前,I/O是这样工作的:               
  1. CPU发出对应的指令给磁盘控制器,然后返回;
  2. 磁盘控制器收到指令后,开始准备数据,会把数据放到磁盘控制器的内部缓冲区中,然后产生一个中断;
  3. CPU收到中断信号后,停下手头的工作,接着把磁盘控制器的缓冲区的数据一次一个字节地读进自己的寄存器,然后再把寄存器里的数据写入到内存,而在数据传输的期间,CPU无法执行其他任务。

    可以看到整个数据传输过程,都需要CPU亲自参与搬运数据的过程,而且这个过程,CPU是不能做其他事情的。简单的搬运几个字符数据是没问题的,但是如果我们用千兆网卡或者硬盘传输大量数据的时候,都用CPU来搬运的话,肯定忙不过来。

    于是DMA技术就诞生了,也就是直接内存访问(Direct Memory Acess)技术。简单理解就是,DMA方式无需处理器的干预,在内存与I/O之间进行快速数据交换。在进行I/O设备的内存的数据传输的时候,数据搬运工作全部交给DMA控制器,而CPU不在参与任何数据搬运相关的事情,这样CPU就可以去处理别的事情。

  1. 用户进程调用read方法,向操作系统发出I/O请求,请求读取数据到自己的内存中,进程进入阻塞状态;

  2. 操作系统收到请求后,进一步将I/O请求发送到DMA,然后让CPU执行其他任务;

  3. DMA进一步将I/O请求发送给磁盘;

  4. 磁盘收到DMA的I/O请求,把数据从磁盘读取到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被读满后,向DMA发起中断信号,告知自己缓冲区已满;

  5. DMA收到磁盘的信号,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,此时不占用CPU,CPU可以执行其他任务;

  6. 当DMA读取了足够多的数据,就会发送中断信号给CPU;

  7. CPU收到DMA的信号,知道数据已经准备好,于是将数据从内核拷贝到用户空间,系统调用返回。

     可以看到,整个数据传输过程,CPU不在参与数据搬运的工作,而是全程由DMA完成,但是CPU在这个过程中也是必不可少的,因为传输什么数据,从哪里传输到哪里,都需要CPU来告诉DMA控制器。
    

4.2 文件传输

    如果服务端要提供文件传输功能,我们能想到的最简单的方式:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。

    传统I/O的工作方式是,数据读取和写入是从用户空间到内核空间来回复制,内核空间的数据是通过操作系统层面的I/O接口从磁盘读取或写入的。![](https://img-blog.csdnimg.cn/direct/e50bddaef5674d2491d5089bb91f3553.png)

    首先,期间共发生了4次用户态与内核态的上下文切换,因为发生了两次系统调用,一次是read,一次是write,每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换会用户态。

    上线文切换成本并不小,一次切换需要耗时几十纳秒到几微妙,虽然看上去时间很短,但是在高并发的场景下,这类时间容易被累计放大,从而影响系统整体性能。

    其次,发生了四次数据拷贝,其中两次是DMA的拷贝,另外两次则是通过CPU拷贝的:
  1. 第一次拷贝:把磁盘上的数据拷贝到操作系统内核的缓冲区里,这个拷贝过程是通过DMA搬运的。

  2. 第二次拷贝:把内核缓冲区的数据拷贝到用户的缓冲区里,于是我们应用程序就可以使用这部分数据了,这个拷贝过程则是由CPU完成的。

  3. 第三次拷贝:把刚拷贝到用户缓冲区里的数据再拷贝到内核socket的缓冲区里,这个过程依然是CPU完成的。

  4. 第四次拷贝:把内核socket缓冲区里的数据,拷贝到网卡缓冲区里,这个过程又是由DMA搬运的。

     看这个文件的传输过程,我们只是搬运一份数据,结果却搬运了4次,过多的数据拷贝无疑会消耗CPU资源,大大降低系统性能。
    
     这种简单又传统的方式,存在冗余的上下文切换和数据拷贝,在高并发系统里是非常糟糕的,多了很多不必要的开销,会严重影响系统性能。所以要想提高文件传输的性能,就要减少用户态与内核态的上下问切换与内存拷贝的次数。
    

4.3 零拷贝

    Linux 内核从 2.4 版本开始,对于网卡支持SG-DMA技术的情况下,sendFile()系统调用的过程发生了变化。
  1. 通过DMA将磁盘上的数据拷贝到内核缓冲区中;

  2. 缓冲区描述符和数据长度传输到socket缓冲区,这样网卡的SG-DMA控制器就可以直接将内核缓冲区中的数据拷贝到网卡缓冲区里,此过程不需要将数据从操作系统内科拷贝到socket缓冲区。​​​​​​​

     就是所谓的零拷贝技术,因为我们没有在内存层面去拷贝数据,也就是全程没有通过CPU来搬运数据,所有的数据都通过DMA来进行传输。与传统方式相比,减少了两次上下文切换和数据拷贝次数,只需要两次上下文切换就可以完成文件传输,而且两次数据拷贝都不要CPU的参与,所以,零拷贝技术可以把文件传输的性能提高至少一倍以上。
    
     kafka在数据传输中尽可能减少数据复制环节,利用操作系统提供的零拷贝技术,将数据直接从文件缓存区传递到网络协议栈,降低了CPU开销和延迟。这是kafka高性能的第四个原因。
    

五、分区并行处理

    这个很好理解,Kafka将主题拆分为多个分区,并且可以在不同的broker节点上分布这些分区,实现了并行处理能力,从而能够扩展以应对高并发场景下的读写需求。

    这是kafka高性能的第五个原因。

六、高效的消息压缩

    支持消息压缩功能,在不影响消息传递速度的同时降低网络带宽消耗,进一步提升整体性能。

    常见的压缩算法是数据量越大压缩效果越好,一条消息通常不会太大,这就导致压缩效果不太好。而Kafka实现的压缩方式是将多条消息一起进行压缩,这样可以保证比较好的压缩效果。        

    producer将消息进行压缩,发往broker,broker保存消息。这里需要注意,producer和broker的压缩算法要一致,broker端有参数可以配置压缩算法,默认值是producer即保持生产者的压缩算法。如果两端的压缩算法不一致,producer用一种压缩算法,发到broker后 broker需要对压缩消息进行解压,解压后在按照broker设置的压缩算法进行压缩,然后在保存,多了许多额外的操作,会导致broker的CPU变高,导致吞吐量下降,效率降低。消费者与broker有同样的问题。常用的压缩算法有GZIO、SNAPPY、LZ4。

    将多条消息进行压缩处理,这是kafka高性能的第六个原因。

七、总结

    关于kafka高性能的原因就介绍到这里,不知道你学会了吗,欢迎留言讨论。

本文转载自: https://blog.csdn.net/qq_39209927/article/details/135775258
版权归原作者 超越不平凡 所有, 如有侵权,请联系我们删除。

“揭秘 Kafka 高性能之谜:一文读懂背后的设计精粹与技术实现”的评论:

还没有评论