在面对一些亿级流量场景,消息队列届的大哥kafka是如何保证高性能的呢?
-
Kafka Reactor模型架构
Kafka客户端和服务端通信采取的是NIO的reactor模式,它是一种事件驱动模式。reactor模型天然支持高并发,可以充分利用多核心多线程。
https://blog.51cto.com/u_15067229/2573746
-
页缓存技术+磁盘顺序写
2.1 操作系统每次从磁盘读写数据的时候,需要先寻址,也就是先要找到数据在磁盘上的物理位置,然后再进行数据读写。如果是机械硬盘,这个寻址需要比较长的时间,因为它要移动磁头,这是个机械运动,机械硬盘工作的时候会发出咔咔的声音,就是移动磁头发出的声音。
顺序读写相比随机读写省去了大部分的寻址时间,它只要寻址一次,就可以连续地读写下去,所以说,性能要比随机读写要好很多。
Kafka 就是充分利用了磁盘的这个特性。它的存储设计非常简单,对于每个分区,它把从Producer 收到的消息,顺序地写入对应的 log 文件中,一个文件写满了,就开启一个新的文件这样顺序写下去。消费的时候,也是从某个全局的位置开始,也就是某一个 log 文件中的某个位置开始,顺序地把消息读出来。
2.2 在 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 资源,间接也提升了写入的性能。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-roiliIY7-1651371448094)(https://www.hengyumo.cn/momoclouddisk/file/download?code=202204162045895_image.png)]](https://img-blog.csdnimg.cn/103c2501a8554f9eb6349f4ba54257e0.png)
-
ZeroCopy:零拷贝技术
我们知道,在服务端,处理消费的大致逻辑是这样的:
首先,从文件中找到消息数据,读到内存中;
然后,把消息通过网络发给客户端。
这个过程中,数据实际上做了 2 次或者 3 次复制:
- 从文件复制数据到 PageCache 中,如果命中 PageCache,这一步可以省掉;
- 从 PageCache 复制到应用程序的内存空间中,也就是我们可以操作的对象所在的内存;
- 从应用程序的内存空间复制到 Socket 的缓冲区,这个过程就是我们调用网络应用框架的 API 发送数据的过程。
Kafka 使用零拷贝技术可以把这个复制次数减少一次,上面的 2、3 步骤两次复制合并成一次复制。直接从 PageCache 中把数据复制到 Socket 缓冲区中,这样不仅减少一次数据复制,更重要的是,由于不用把数据复制到用户内存空间,DMA 控制器可以直接完成数据复制,不需要 CPU 参与,速度更快。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k1I5XinF-1651371448095)(https://www.hengyumo.cn/momoclouddisk/file/download?code=202204162044983_image.png)]](https://img-blog.csdnimg.cn/3715fe58ba4f4f1189c20435e6418de5.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OYKt2kL1-1651371448096)(https://www.hengyumo.cn/momoclouddisk/file/download?code=202204162044407_image.png)]](https://img-blog.csdnimg.cn/12b5c6db0cbd4d60b9ee63983cfd8fb3.png)
- 使用批量消息提升服务端处理能力
我们知道,批量处理是一种非常有效的提升系统吞吐量的方法。在 Kafka 内部,消息都是以“批”为单位处理的。一批消息从发送端到接收端,是如何在 Kafka 中流转的呢?
我们先来看发送端,也就是 Producer 这一端。
在 Kafka 的客户端 SDK(软件开发工具包)中,Kafka 的 Producer 只提供了单条发送的send() 方法,并没有提供任何批量发送的接口。原因是,Kafka 根本就没有提供单条发送的功能,是的,你没有看错,虽然它提供的 API 每次只能发送一条消息,但实际上,Kafka的客户端 SDK 在实现消息发送逻辑的时候,采用了异步批量发送的机制。
当你调用 send() 方法发送一条消息之后,无论你是同步发送还是异步发送,Kafka 都不会立即就把这条消息发送出去。它会先把这条消息,存放在内存中缓存起来,然后选择合适的时机把缓存中的所有消息组成一批,一次性发给 Broker。简单地说,就是攒一波一起发。在 Kafka 的服务端,也就是 Broker 这一端,又是如何处理这一批一批的消息呢?
在服务端,Kafka 不会把一批消息再还原成多条消息,再一条一条地处理,这样太慢了。
Kafka 这块儿处理的非常聪明,每批消息都会被当做一个“批消息”来处理。也就是说,在Broker 整个处理流程中,无论是写入磁盘、从磁盘读出来、还是复制到其他副本这些流程中,批消息都不会被解开,一直是作为一条“批消息”来进行处理的。
在消费时,消息同样是以批为单位进行传递的,Consumer 从 Broker 拉到一批消息后,在客户端把批消息解开,再一条一条交给用户代码处理。
比如说,你在客户端发送 30 条消息,在业务程序看来,是发送了 30 条消息,而对于Kafka 的 Broker 来说,它其实就是处理了 1 条包含 30 条消息的“批消息”而已。显然处理 1 次请求要比处理 30 次请求要快得多。
构建批消息和解开批消息分别在发送端和消费端的客户端完成,不仅减轻了 Broker 的压力,最重要的是减少了 Broker 处理请求的次数,提升了总体的处理能力。
https://blog.csdn.net/qq_4278923/article/details/106785426
https://blog.csdn.net/weixin_38499215/article/details/98750908
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)