近些年,项目中基本是离不开消息队列,消息队列的存在可以给我解决很多问题,特别是在数据量并发很大的情况下,带来的收益是很客观的,因此很多消息队列的框架都创建的出来,比如RabbitMQ,ActiveMQ,Kafka,RocketMQ,每个消息都有优劣,在这里我们只谈论Kafka,因为项目中用到的是Kafka哈哈哈,抱歉哈,目前只能说说Kafka,后面有机会可以说说其他的消息队列,比如RabbitMQ,虽然项目中也用到了,但是了解不是很深入,也就不误导大家了。

下面还是通过几个方面来说明Kafka的性能为什么这么好。

1.顺序写入磁盘,增加IO性能

这里就需要介绍一下,Kafka是s怎么存储消息的

一般持久的设计一般都是先写入内存然后再刷新到磁盘,在我理解的,mysql和Redis的缓存就是这样的,因为磁盘的直接写入速度一直都是不怎么友好的,但是在一些情况下,直接写入磁盘的速度反而是要高于写入内存的,比如顺序写入磁盘的速度是要快于随机写入内存的,Kafka就是采用了顺序写入的方式,每次新的内容写入都是采用文件追加的方式,这也就以为着每次新写入的数据都是在文件的结尾,并且对于之前已经写入的内容是不能够进行修改的。但顺序写入的方式并不能够把快体现的很明显,还要结合下面的页缓存机制。

2.页缓存pageCache

图来源于网络
在这里插入图片描述

上面说到了在消息写入是顺序IO,所以速度很快,那么在查询的时候,Kafka又是怎么来提高性能的呢?

如上图,是通过PageCache的方式,Kafka会将一些热点数据放在PageCache中,那如何定义热点数据呢,为最近访问的数据,因为最近访问的数据再次被访问到的机率还是很大的,当查询数据时,先从PageCache中进行查找,如果PageCache没有,再去磁盘中查找,并将磁盘中的数据拷贝到PageCache中。这样就可以避免每次数据查询都直接去磁盘查询,因为每次的磁盘查询就是很"耗费时间"的。

这里其实还有一个预查询的概念,正因为磁盘查询性能低,如果一次没有查到还会进行第二次,所以在第一次查询的时候,PageCache会进行预查询的操作,比如需要查询0-32k的数据,PageCache会将32-64k的数据也加载进来,增加查询的命中率。

还有在大文件查询的情况下,是不宜用PageCache的,因为大文件的一次加载可能直接把PageCache的空间占满,而且大文件拷贝到PageCache的开销也是很大的。

3.零拷贝

因为Kafka的消息是存在磁盘的,消息是在生产者,Broker,消费者中进行网络传输的,这里就涉及到了消息在磁盘到网络的转换。而零拷贝的作用就是通过减少用户态和内核态的转换,从而减少消息在磁盘到网络传输的资源损耗。

通过SendFile的方式来较少用户态到内核态状态的转换,从而提高文件传输的速率,那么什么是用户态和内核态呢,这一点就需要大家去看看操作系统相关的知识了,后面有空我可能也会做详细的介绍。

我们先来看看一次文件的读取,其中用户态和内核态的转换过程
图来源于网络
在这里插入图片描述

在这里发生了四次状态转换和四次拷贝,四次状态转换分别是一次write()和一次read(),每次都会对应两次的用户态和内核态的切换,四次拷贝分别是两次DMA拷贝和两次cpu拷贝。想要提高性能必须减少状态切换次数和文件的拷贝次数。

零拷贝技术就是为了解决这个问题,一般实现零拷贝有两个方式,分别为mmap+write和sendFile,我们都来讲一下各自的实现方式。

  • mmap+write
    图来源于网络
    在这里插入图片描述

在这里主要改变是当内核态中将磁盘文件拷贝到内核的缓冲区后,通过mmap会将这部分数据进行共享,用户态可以直接访问,同时数据直接从内核的缓冲区拷贝到socket缓冲区,不需要经过用户态了。

这里的优化可以减少一次数据拷贝,但是状态切换还是4次,总体来说优化并不是很大。

通过sendfile来代替一次read()和write(),从而就能减少两次状态的切换,同时拷贝次数还是3次。

这里其实还存在一定的优化,那就是在cpu拷贝内核的缓冲区的数据到socket缓冲区,如果网卡支持 SG-DMA,那么可以通过SG-DMA来将数据直接拷贝到网卡,减少cpu拷贝的过程,最终的效果如下:
图来源于网络
在这里插入图片描述

其实零拷贝,并不是说拷贝的次数为零,只是说没有cup拷贝的过程,这里的零拷贝指的是cpu拷贝次数为零,希望小伙伴们能够记住哦-

4.网络数据采用压缩算法

在Kafka中消息是在生产者,Broker,消费者进行传输的,Kafka采用的数据压缩的方式,以时间换空间,通过cpu时间的增加来尽量的减少磁盘空间的占用和网络IO的传输量,Kafka中消息的压缩是发生在生产者和Broker端的。

在生产者端,消息发送的时候将消息进行压缩,可以通过参数来配置进行压缩的算法,常见的算法有GZIP,Snappy,zstd,zlib等等,小伙伴们可以去了解一下。一般来说,在Broker端接受到消息直接持久化,然后交给消费者,那么刚刚说的Borker的压缩又是在什么情况下产生的呢?在这里有两种情况,一种是进行消息的检验,Broker端会对生产者发送的消息进行验证,对消息有一个解压和重新压缩的过程,另一种情况是在集群的环境下,多个Broker的版本不一致,新版本需要兼容老版本的数据格式,所以需要对数据进行解压格式转换,然后再重新压缩。

在消费者端就需要对消息进行解压缩操作,这里有个疑问,消费者是如何知道发过来的数据包是采用什么压缩算法呢?不知道压缩算法是无法对数据进行解压的,这里Kafka是在发送消息的时候,会将采用的压缩算法也会放入消息中,这样在消费者接受到消息之后,就知道是采用什么压缩算法去进行解压啦。

关于Kafka压缩算法的开启,建议是在cpu资源充足的情况下,并且特别是在带宽资源有限的情况下,消息压缩的开启缓解很大的压力。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐