Kafka


Kafka

为什么 Kafka 写得这么快?

Kafka 写入速度非常快,主要得益于其系统架构设计,包括:

  • PageCache
  • 批量压缩传输
  • 顺序、批量写磁盘
  • 多 partition 分散存储

PageCache

Kafka broker 写入消息的时候,其实并不是直接写入文件,而是写入系统的 PageCache 内存,后续才有操作系统刷入文件中。通过这种方式,Kafka broker 就不直接写文件,而是直接写内存,这样就非常快速了!

批量压缩传输

在很多情况下,系统的瓶颈不是 CPU 或磁盘,而是网络带宽,对于需要在广域网上的数据中心之间发送消息的数据流水线尤其如此。所以数据压缩就很重要。可以每个消息都压缩,但是压缩率相对很低。所以 Kafka 使用了批量压缩,即将多个消息一起压缩而不是单个消息压缩。

Kafka 允许使用递归的消息集合,批量的消息可以通过压缩的形式传输并且在日志中也可以保持压缩格式,直到被消费者解压缩。Kafka 支持 Gzip 和 Snappy 压缩协议。

顺序、批量写磁盘

第一,顺序写入磁盘。我们知道现在的磁盘大多数都还是机械结构(SSD不在讨论的范围内),如果将消息以随机写的方式存入磁盘,就会按柱面、磁头、扇区的方式进行(寻址过程),缓慢的机械运动(相对内存)会消耗大量时间,导致磁盘的写入速度只能达到内存写入速度的几百万分之一,为了规避随机写带来的时间消耗,KAFKA 采取顺序写的方式存储数据,这样就减少了寻址的时间,极大地提高了写入速度。

第二,批量写入磁盘。另外,kafka 写入消息的时候,是批量写入消息,而不是一次发送一条消息,这样极大地提高了效率。

第三,无效率的字节复制。Kafka 采用由 Producer,broker 和 consumer 共享的标准化二进制消息格式,这样数据块就可以在它们之间自由传输,无需转换,降低了字节复制的成本开销。 TODO 搞清楚,这里不太清晰。

多 partition 分散存储

Kafka 存储的特点是小文件,并且切分成多个 Partition,分散在多个机器。这样读取的时候就可以充分利用磁盘的 IO,从而达到高效读取的目的。试想,如果文件太大,并且只存在一个磁盘,那么该磁盘的压力得有多大?

总结一下:

Kafka 的写入流程可以分为将数据传输到 Kafka 服务器,之后将数据写入磁盘。在数据传输阶段,Kafka 利用批量加压缩的方式,极大地提升了每次能发送的数据量,从而提高了写入速度。而在写入磁盘环节,通过存储结构的设计,使得 Kafka 可以批量、顺序写入,从而减少磁盘寻道的时间。并且通过小文件的存储方式,提高了整体磁盘的耐受力。

批量写入、压缩的传输方式,与磁盘顺序写入、小文件多 partition 造就了 Kafka 强悍的写入速度!

Kafka 为什么读得这么快?

Kafka 之所以能够达到百万级别的读取速度,很大原因是得益于其存储结构设计,以及其对读取流程的优化。简单地说,有下面几个关键点,使其能读得这么快:

  • PageCache
  • index/log 存储结构
  • zero-copy

PageCache

Kafka 一般是来了立刻会被消费,由于 broker 写入消息之前会写入 PageCache,那么基本上都可以从 PageCache 中找到消息。而 Kafka broker 读取消息的时候,可以直接中 PageCache 中读取消息,通过这种方式就可以不需要去加载磁盘文件,直接读取内存文件就好了,这样极大地加快了读取速度。

index/log 存储结构

在之前的章节中,我们讲过 Kafka 数据的存储方式,其实通过稀疏索引的方式,将文件所存储的消息范围体现在文件名上。通过这种方式,我们仅仅通过文件名就可以定位到消息所处文件。而我们只需要将所有文件名读取到内存中,用二叉搜索树就可以快速定位到目标文件。当我们定位到目标文件之后,又因为其实顺序写入,因此通过二分法可以快速地定位到目标消息。

因此,index/log 的存储方式可以帮助我们快速定位到目标消息。

zero-copy

定位到目标文件消息之后,如何快速地将文件发送出去呢?这就涉及到操作系统对于文件的读取流程了。而 zero-copy 其实就是操作系统用来优化文件读取速度的,简单地说就是尽量减少不必要的环节,提高整体的发送速度。

如果你想详细了解 zero-copy 的内容,可以参考 「什么是 zero-copy?」 章节。

总结一下 Kafka 读得这么快的原因:

Kafka 读取数据的过程可以分为两个过程,一个是定位到数据文件的过程,一个是将数据文件发送出去的过程。Kafka 的 index/log 稀疏索引的设计方式,使得 Kafka 可以快速定位到目标文件,而 Kafka 利用 zero-copy 技术可以将文件快速发送出去。这两者的结合造就了 Kafka 的读取速度异常地快!

聊聊 Kafka 的消息处理过程

Kafka 的消息流转可以分为下面几个阶段:

  1. 服务器启动阶段
  2. 生产者发送消息阶段
  3. Kafka存储消息阶段
  4. 消费者拉取消息阶段

服务器启动阶段

首先需要启动 zk 服务器,接着 Kafka 启动后会向 zk 注册服务器信息,启动线程池监听客户端的连接请求。

生产者发送消息阶段

当需要将消息存入消息队列中时,生产者根据配置的分片算法,选择分到哪一个 partition 中。随后生产者与该 Partition Leader 建立联系,之后将消息发送至该 partition leader。之后生产者会根据设置的 request.required.acks 参数不同,选择等待或或直接发送下一条消息。

  • request.required.acks = 0 表示 Producer 不等待来自 Leader 的 ACK 确认,直接发送下一条消息。在这种情况下,如果 Leader 分片所在服务器发生宕机,那么这些已经发送的数据会丢失。
  • request.required.acks = 1 表示 Producer 等待来自 Leader 的 ACK 确认,当收到确认后才发送下一条消息。在这种情况下,消息一定会被写入到 Leader 服务器,但并不保证 Follow 节点已经同步完成。所以如果在消息已经被写入 Leader 分片,但是还未同步到 Follower 节点,此时Leader 分片所在服务器宕机了,那么这条消息也就丢失了,无法被消费到。
  • request.required.acks = -1 表示 Producer 等待来自 Leader 和所有 Follower 的 ACK 确认之后,才发送下一条消息。在这种情况下,除非 Leader 节点和所有 Follower 节点都宕机了,否则不会发生消息的丢失。

Kafka存储消息阶段

当 Kafka Server 接收到消息后,其并不直接将消息写入磁盘,而是先写入内存中。之后根据生产者设置参数的不同(request.required.acks 参数),选择是否回复 ack 给生产者。有一个线程会定期将内存中的数据刷入磁盘,这里有两个参数控制着这个刷过程:

# 数据达到多少条就将消息刷到磁盘
#log.flush.interval.messages=10000
# 多久将累积的消息刷到磁盘,任何一个达到指定值就触发写入
#log.flush.interval.ms=1000

如果我们设置 log.flush.interval.messages=1,那么每次来一条消息,就会刷一次磁盘。通过这种方式,就可以达到消息绝对不丢失的目的,这种情况我们称之为同步刷盘。 反之,我们称之为异步刷盘。

与此同时,Kafka 服务器也会进行副本的复制,该 Partition 的 Follower 会从 Leader 节点拉取数据进行保存。然后将数据存储到 Partition 的 Follower 节点中。

消费者拉取消息阶段

在消费者启动时,其会连接到 zk 注册节点。消费者根据所连接 topic 的 partition 个数和消费者个数,根据分配算法进行 partition 分配。一个 partition 最多只能被一个线程消费,但一个线程可以消费多个 partition。

上次编辑于: 2022/7/30 09:07:48