面试官:手写个高性能kafka?

作为MQ,Kafka的性能说第二,恐难有人敢说第一。一台配置较好的服务器,对Kafka做极限性能压测,Kafka单节点的极限处理能力接近2000万条消息/s,吞吐量达600MB/s。

像全异步化的线程模型、高性能的异步网络传输、自定义的私有传输协议和序列化、反序列化等等,这些方法和优化技巧,Kafka都做到了。

性能优化除了这些通用手段,它还有啥葵花宝典般神技呢?

批量消息提升服务端处理能力

批量处理是一种非常有效的提升系统吞吐量的方法。
Kafka内部的消息都是以“批”为单位处理。
一批消息从发送端到接收端,是如何在Kafka中流转的呢?

Producer端

在Kafka的客户端SDK,Kafka的Producer只提供了单条发送的send()方法,并没有提供任何批量发送的接口。原因是,Kafka根本就没有提供单条发送的功能,是的,你没有看错,虽然它提供的API每次只能发送一条消息,但实际上,Kafka的客户端SDK在实现消息发送逻辑的时候,采用了异步批量发送。

当调用send()方法发送一条消息之后,无论你是同步发送还是异步发送,Kafka都不会立即把这消息发出去。猥琐发育一波再打个团战:

  • 先把消息缓存在内存
  • 然后选择合适时机把缓存的所有消息组成一批,一次性发给Broker

Kafka服务端,即Broker端,又是如何处理这一批批消息的呢?
服务端,Kafka不会把一批消息再还原成多条消息,再一条条处理,这样太慢了。
而是每批消息都会被当做一个“批消息”处理。即在Broker整个处理流程,无论是写入磁盘、从磁盘读出、还是复制到其他副本这些流程中,批消息都不会被解开,一直是作为一条“批消息”来进行处理的。

在消费时,同样是以批为单位传递,Consumer从Broker拉到一批消息后,在客户端把批消息解开,再一条条交给用户代码。

比如说,你在客户端发30条消息,在业务程序看,是发送了30条消息,而对于Kafka的Broker来说,它其实就是处理了1条包含30条消息的“批消息”。显然处理1次请求要比处理30次请求快得多。

构建批消息和解开批消息分别在发送端和消费端的客户端完成,不仅减轻Broker压力,减少了Broker处理请求的次数,提升了总体的处理能力。
这就是Kafka用批量消息提升性能的方法。

相比于网络传输和内存,磁盘IO的速度是比较慢的。对于消息队列的服务端来说,性能的瓶颈主要在磁盘IO这一块。

顺序读写提升磁盘IO性能

磁盘有个特性:顺序读写性能远好于随机读写。
在SSD上,顺序读写的性能要比随机读写快几倍,如果是机械硬盘,这个差距会达到几十倍。

os每次从磁盘读写数据时,需先寻址,即找到数据在磁盘的物理位置,然后再读写数据。
若是机械硬盘,寻址需要较长时间,因为要移动磁头。顺序读写相比随机读写省去大量寻址时间,只要寻址一次,就可连续读写下去,所以性能比随机读写好。

Kafka充分利用磁盘特性。存储设计非常简单,对每个分区,它把从Producer收到的消息,顺序地写入对应log文件,一个文件写满,就开启新文件顺序写。
消费时,也是从某个全局位置开始,即某个log文件的某位置开始,顺序读出消息。

这简单的设计,充分利用顺序读写特性,极大提升Kafka在使用磁盘时的IO性能。

PageCache加速消息读写

PageCache是os在内存中给磁盘的文件建立的缓存。
无论使用什么高级语言,在调用系统API读写文件时,并不会直接去读写磁盘的文件,实际操作的都是PageCache,即文件在内存中缓存的副本。

应用程序在写入文件时,操作系统会先把数据写入到内存中的PageCache,再一批批写到磁盘。
读取文件的时候,也是从PageCache中来读取数据,这时候会出现两种可能情况。

  1. PageCache中有数据,直接读取,这样就节省了从磁盘上读取数据的时间;另一种情况是,PageCache中没有数据,这时候操作系统会引发一个缺页中断,应用程序的读取线程会被阻塞,操作系统把数据从文件中复制到PageCache中,然后应用程序再从PageCache中继续把数据读出来,这时会真正读一次磁盘上的文件,这个读的过程就会比较慢。

应用程序使用完某块PageCache后,os并不会立刻清除该PageCache,而是尽可能地利用空闲的物理内存保存这些PageCache,除非系统内存不够用,操作系统才会清理部分PageCache。清理的策略一般是LRU或它的变种算法:优先保留最近一段时间最常使用的那些PageCache。

Kafka在读写消息文件的时候,充分利用了PageCache的特性。一般来说,消息刚刚写入到服务端就会被消费,按照LRU的“优先清除最近最少使用的页”这种策略,读取时候,对于这种刚刚写入的PageCache,命中的几率会非常高。

大部分情况下,消费读消息都会命中PageCache,带来的好处有:

  • 读取的速度会非常快
  • 给写入消息让出磁盘的IO资源,间接也提升了写入的性能

零拷贝

Kafka的服务端在消费过程中,还使用了一种“零拷贝”的操作系统特性来进一步提升消费的性能。

在服务端,处理消费的大致逻辑是这样的:

  • 首先,从文件中找到消息数据,读到内存中
  • 然后,把消息通过网络发给客户端

数据实际上做了2次或者3次复制:

  1. 从文件复制数据到PageCache中,如果命中PageCache,这一步可省
  2. 从PageCache复制到应用程序的内存空间中,也就是我们可以操作的对象所在的内存
  3. 从应用程序的内存空间复制到Socket的缓冲区,这过程就是我们调用网络应用框架API发送数据的过程

Kafka使用零拷贝技术可把这复制次数减少一次,上面的2、3步骤两次复制合并成一次复制。
直接从PageCache中把数据复制到Socket缓冲区中,这样不仅减少一次数据复制,更重要的是,由于不用把数据复制到用户内存空间,DMA控制器可以直接完成数据复制,不需要CPU参与,速度更快。

下面是这个零拷贝对应的系统调用:

#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

它的前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。

如果你遇到这种从文件读出数据后再通过网络发送出去的场景,并且这过程中你不需对这些数据处理,那一定要使用零拷贝方法,有效提升性能。

全部评论

相关推荐

10-14 12:11
门头沟学院 C++
俗话说天上一天,人间十年,现在我对这句话有了进一步的理解,那就是在学校一天=寿命减十天。研究方向是纯理论的(TCS),先不说文章很难出来,就算中了文章对找工作也是0帮助,甚至有些企业会认为研究成果过于理论化而对你减分。这个方向的科研界和工业界完全脱离,就算读到博士也只能去卷教职。明年6月就要毕业了,小论文最近才投了一个CCFB会议&nbsp;,中不中另说,后续返修还要投入大量时间,专利(保毕业)还要写、改、申,中文版大论文还停留在研二开题的状态。回顾研究生生涯,从大四下学期进组学习,直到论文产出,大约是两年半的时间,这两年半都被锁在实验室(工作日打卡有严格要求,两天不打会被约谈),期间挨骂(科研进度或缺打卡)、被pua的次数数不胜数,直接导致了我从e人成为了i人,不爱说话、脱发严重、体态变差、心理素质变差等等并发因素接踵而至,也完全丧失了两年前对科研的热情、信心,看透了学术界的藏污纳垢。再说秋招,从八月份紧赶慢赶做简历、写项目、投简历、刷题,到现在累计投递约300家了,面试的次数是个位数,而且没有一个是真心想招我的,全都是问点八股没有手撕,20分钟结束就没下文的那种。现在论文投了想出去实习,老板也不放人,说要我带师弟继续做,榨干最后一点剩余价值。所以读研对人来说收获了什么呢?如果能够重来,我宁愿去送外卖(没有瞧不起送外卖的意思)也不愿再选择读这个B书。 #读研#&nbsp;&nbsp;#读研无意义#&nbsp;&nbsp;#研究生#&nbsp;&nbsp;#秋招#
希望奇迹发生的杰克很友好:哥们儿,我是一直干横向,科研进度没有,现在是一手抓科研一手抓毕业一手抓就业,人都要疯了
点赞 评论 收藏
分享
4 18 评论
分享
牛客网
牛客企业服务