Kafka 为什么这么快?

Kafka 为什么这么快?

MQ的消息存储由几种选择:

  • 一:内存
  • 二:第三方存储,比如数据库
  • 三:磁盘

内存中存储速度快,但是不可靠,服务器宕机数据丢失,第三方存储会产生网络消耗,而且三方出现问题也会影响储存。所以最常见的是把数据放在磁盘上。

一:顺序读写

之前讲Mysql的时候提到了一种思路,写数据文件data file 和写用来崩溃恢复的redo log的区别在哪里,这个就是随机IO和顺序IO 。
在这里插入图片描述

因为完成一次磁盘 IO,需要经过寻道、旋转和数据传输三个步骤。影响磁盘 IO 性能的因素也就发生在这三个步骤上:

  • 寻道时间:平均 3-15ms
  • 旋转延迟:这个由磁盘转速决定,7200rpm 的磁盘平均旋转延迟大约为 60*1000/7200/2 = 4.17ms,而转速为 15000rpm 的磁盘其平均旋转延迟为 2ms
  • 数据传输时间:数据大小除以数据传输率,取决于数据传输率(目前300MB/s),数据传输时间通常远小于前两部分消耗时间,简单计算时可忽略。

因此,如果在写磁盘的时候省去寻道、旋转可以极大地提高磁盘读写的性能。磁盘的顺序读写可以达到50M/s左右,比内存的随机读写还要快。

Kafka 中每个分区是一个有序的,不可变的消息序列,新的消息不断追加到 Partition 的末尾,在 Kafka 中 Partition 只是一个逻辑概念,Kafka 将 Partition 划分为多个Segment,每个 Segment 对应一个物理文件,Kafka 对 segment 文件追加写,这就是顺序写文件,基本减少了磁盘寻道和旋转的次数,达到提高写入的吞吐量。

二:批量读写和文件压缩

Kafka Producer 原理中说过,向 Broker 发送消息不是一条消息一条消息的发送。Producer 有两个重要的参数:batch.sizelinger.ms。这两个参数就和 Producer 的批量发送有关。

而且发送数据的过程中,KafkaProducer —>ProducerInterceptor—>Serializer—>Partitioner 这个之后就是Compress(压缩)默认情况下,在 Kafka 生产者中不启用压缩,开启 压缩 有助于提高吞吐量,减少网络IO,并提高磁盘利用率。

Kafka 支持多种压缩算法:lz4、snappy、gzip。Kafka 2.1.0 正式支持 ZStandard —— ZStandard 是 Facebook 开源的压缩算法

三:索引

上面说过Kafka 对 segment 文件追加写,Kafka 每个分区日志在物理上实际按大小被分成多个 Segment。

Segment由 2 大部分组成,分别为 index file 和 data file,此 2 个文件一一对应,成对出现,后缀”.index”和“.log”分别表示为 segment 索引文件、数据文件。

index 采用稀疏索引,这样每个 index 文件大小有限,Kafka 采用mmap的方式,直接将 index 文件映射到内存,这样对 index 的操作就不需要操作磁盘 IO。mmap的 Java 实现对应 MappedByteBuffer 。

Kafka 充分利用二分法来查找对应 offset 的消息位置:

  • 按照二分法找到小于 offset 的 segment 的.log 和.index
  • 用目标 offset 减去文件名中的 offset 得到消息在这个 segment 中的偏移量。
  • 再次用二分法在 index 文件中找到对应的索引。
  • 到 log 文件中,顺序查找,直到找到 offset 对应的消息。

三:零拷贝

零拷贝就是尽量去减少数据的拷贝次数,从而减少拷贝的 CPU 开销,减少用户态内核态的上下文切换次数,从而优化提高数据传输的性能。

Kafka 使用到了 mmap 和 sendfile 的方式来实现零拷贝。分别对应 Java 的 MappedByteBuffer 和 FileChannel.transferTo。

kafka Consumer 从 Broker 消费数据,Broker 读取 Log,就使用了 sendfile。

四:PageCache

producer 生产消息到 Broker 时,Broker 会使用 pwrite() 系统调用【对应到 Java NIO 的 FileChannel.write() API】按偏移量写入数据,此时数据都会先写入page cache。consumer 消费消息时,Broker 使用 sendfile() 系统调用【对应 FileChannel.transferTo() API】,零拷贝地将数据从 page cache 传输到 broker 的 Socket buffer,再通过网络传输。

leader 与 follower 之间的同步,与上面 consumer 消费数据的过程是同理的。

page cache中的数据会随着内核中 flusher 线程的调度以及对 sync()/fsync() 的调用写回到磁盘,就算进程崩溃,也不用担心数据丢失。另外,如果 consumer 要消费的消息不在page cache里,才会去磁盘读取,并且会顺便预读出一些相邻的块放入 page cache,以方便下一次读取。

因此如果 Kafka producer 的生产速率与 consumer 的消费速率相差不大,那么就能几乎只靠对 broker page cache 的读写完成整个生产 - 消费过程,磁盘访问非常少。

五:分区并发

Kafka 的 Topic 可以分成多个 Partition,每个 Paritition 类似于一个队列,保证数据有序。同一个 Group 下的不同 Consumer 并发消费 Paritition,分区实际上是调优 Kafka 并行度的最小单元,因此,可以说,每增加一个 Paritition 就增加了一个消费并发。

Kafka 消费原理中讲到过对应的分区分配算法范围分配、轮询分配、粘带分配还可以自定义分配。

那是不是分区数越多越好呢?

当然不是

  • 越多的分区需要打开更多的文件句柄,必要时需要调整操作系统允许打开的文件句柄数。
  • 客户端 / 服务器端需要使用的内存就越多,producer 有个参数 batch.size,默认是 16KB。它会为每个分区缓存消息,如果分区数越多,这部分缓存所需的内存占用也会更多
  • 降低高可用性,分区越多,每个 Broker 上分配的分区也就越多,当一个发生 Broker 宕机,那么恢复时间将很长。

所以还是根据自己的业务需要调整。

六、总结

Kafka 性能优化:

  • 零拷贝网络和磁盘
  • 优秀的网络模型,基于 Java NIO
  • 高效的文件数据结构设计,轻量级 offset(稀疏索引)
  • Parition 并行和可扩展
  • 数据批量传输
  • 数据压缩
  • 顺序读写磁盘
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值