程序员老鬼

字节面试官:你说说 Kafka 为什么是高性能的?

不得不说,面试中的“灵魂拷问”真是五花八门,但有一个问题每次听到都能让人瞬间清醒,那就是:“Kafka 为什么是高性能的?”

讲真,这个问题不仅是面试官在考你技术深度,更是在摸你对架构设计的理解。这不,我今天就化身“面试者”,用程序员的视角,和大家聊聊 Kafka 高性能背后的秘密,顺便给点我的理解。

一、Kafka 高性能的秘诀之一:顺序写磁盘

要理解 Kafka 高性能的核心,得先聊聊“顺序写磁盘”这件事儿。Kafka 的消息是以日志文件的形式存储的,而日志的写入方式是顺序写。

为什么顺序写这么香?
相比随机写,顺序写的磁盘寻址操作更少,IO性能会高出几个数量级。打个比方,随机写就像在图书馆里到处找一本书,东翻翻西找找;而顺序写就是你把书架上的书一摞一摞按顺序放上去,不用动来动去,效率直接拉满。

代码举个栗子,Kafka 的日志存储底层代码用的是 FileChannel,配合 mmap 技术优化磁盘操作:

FileChannel channel = FileChannel.open(Paths.get("kafka-log"), StandardOpenOption.WRITE);
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello Kafka".getBytes());
buffer.flip();
channel.write(buffer); // 顺序写

简单几行代码,却承载着 Kafka 高效的日志存储能力。

二、分区+分片:并发的魔法师

Kafka 的另一个高性能法宝是分区机制。一个主题(Topic)可以被分成多个分区,每个分区独立存储,独立消费。这样,生产者和消费者就能并发处理数据,充分利用多核 CPU 和多台机器的性能。

来个生活化的类比:假如你是快递员,原本只有你一个人送全城快递,效率肯定低。但如果把全城分成几个片区,每个片区分给一个快递员,那效率是不是蹭蹭往上涨?Kafka 的分区机制干的就是这事。

分区内的数据是有序的,但跨分区的数据可能是无序的。这就引出了一个设计的取舍问题:性能 vs 有序性。Kafka 选择了性能优先,这也是为什么在消费端需要手动处理消息顺序。

代码层面看分区的创建和分配逻辑时,可以从生产者端找到答案:

ProducerRecord<String, String> record = new ProducerRecord<>("topic", "key", "value");
int partition = Math.abs(record.key().hashCode()) % numberOfPartitions;

通过 key 的 hash 值计算分区,确保同一个 key 总是落在同一个分区。

三、零拷贝:操作系统的“借力打力”

零拷贝(Zero Copy)是 Kafka 在数据传输时的杀手锏。它利用了操作系统提供的 sendfile 系统调用,让数据直接在内核空间里“飞”,而不是绕道用户空间,减少了 CPU 和内存的开销。

传统的数据传输流程可能是这样:

  1. 从磁盘读到内核缓冲区;
  2. 从内核缓冲区拷贝到用户缓冲区;
  3. 从用户缓冲区再拷贝到 Socket 缓冲区;
  4. 发送到网络。

而零拷贝优化后:

  1. 磁盘数据直接拷贝到 Socket 缓冲区;
  2. 发送到网络。

Kafka 的日志存储采用这种方式,提高了吞吐量。代码里其实是依赖 Java 的 FileChannel.transferTo 方法来实现的:

FileChannel fileChannel = FileChannel.open(Paths.get("kafka-log"), StandardOpenOption.READ);
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9092));
fileChannel.transferTo(0, fileChannel.size(), socketChannel); // 零拷贝

四、批处理:拥抱大吞吐量

Kafka 在生产和消费消息时,都会采用批处理的方式。批处理就是将多条消息攒到一起,再进行网络传输或写磁盘。一次性搞定多条消息,显然要比一条一条传省事儿。

批处理有两个关键参数:批次大小(batch.size)和延迟时间(linger.ms)。简单来说:

  • 批次大小控制单次发送的消息量;
  • 延迟时间控制数据在缓冲区的等待时间。

比如生产者的配置:

Properties props = new Properties();
props.put("batch.size", 16384); // 16KB 的批次大小
props.put("linger.ms", 5);     // 最多等待 5ms

Kafka 会尽量把消息攒到批次中,如果超过了批次大小或延迟时间,就会发送。这种方式既提高了吞吐量,又减少了网络的频繁通信。

五、消息压缩:传输效率再提升

Kafka 支持消息压缩,比如 GZIP、Snappy 等。压缩后的消息占用更少的带宽,传输速度也更快。虽然压缩需要额外的 CPU 开销,但在网络传输中,这点成本和带宽节省比起来,简直不值一提。

生产者代码中可以简单开启压缩:

Properties props = new Properties();
props.put("compression.type", "gzip"); // 开启 GZIP 压缩

六、数据复制:性能与可靠性兼顾

Kafka 通过副本(Replica)机制保证数据可靠性,但副本同步并没有影响性能。它采用异步复制方式:Leader 分区负责处理写请求,Follower 分区异步拉取数据。这种设计既保证了性能,又避免了过多的延迟。

要注意,ISR(In-Sync Replica)机制在这里发挥了重要作用。如果 Leader 挂掉了,Kafka 会从 ISR 中选出新的 Leader,确保消息不丢失。

当然了,光会说不够,得多看源码,多敲代码。面试官要是再问我这个问题,我就甩出这些点,外加点代码,看看他还能接着怎么问~ 😂

那么问题来了:你觉得 Kafka 的哪一点最让你觉得“妙不可言”?评论区见~

-END-

ok,今天先说到这,老规矩,给大家分享一份不错的副业资料,感兴趣的同学找我领取。

Image

以上,就是今天的分享了,看完文章记得右下角给何老师点赞,也欢迎在评论区写下你的留言。