为什么kafka这么香

kafka不仅拥有其他消息队列无法媲美的生态,还以大数据情况下的高性能著称,下面是为啥如此高性能的一些细节,看看kafka系统设计的巧妙之处

1.使用批处理

减少网络通信开销

在系统设计中,批处理是提高系统吞吐量的有效方法,kafka也是采用了这种方法,来看看一条消息从发送端到接收端的流转情况

kafka的api只提供了单条消息发送的send()方法,而客户端在发送消息的时候,采用了异步批量发送的方式,当调用一次send()方法的时候,客户端并没有立即将消息发出去,而是在内存中缓存起来,等到时机合适的时候组成一批发出去,也就是攒一波再发,以达到批处理的效果,虽然这可以很大程度的提高系统的吞吐量,但是消息在客户端累计的做法可能会不满足一些业务场景的实时性需求,所以还是之前讨论过的,在消息队列的应用场景中,kafka还是适合大吞吐量的大数据情况下,对于实时性要求比较高的电商领域,阿里采用了自研RocketMQ,京东采用了自研JMQ。当然这篇文章我们是来讨论为什么kafka这么牛的

当消息发送到kafka的服务端之后,服务端并不会解开这个批消息,而是直接将这个“批消息”作为一个单位来处理,在消费者进行消费的时候,消费者从服务端获取到这个批消息,然后在自己的客户端将这个“批消息”解开

这样从整个消息流程来看,假如说一个批消息包括30条消息,原本客户端需要发送30次请求,现在只需要发送一次请求,可以很大程度的提高服务端的性能,减轻了Broker的压力。

2.使用顺序读写提升磁盘IO性能

减少磁盘寻道开销

对于一个磁盘来讲,顺序读写的性能要远远大于随机读写的性能,对于一个磁盘IO来讲,磁头寻址时间是主要的时间消耗,kafka就是采用了顺序读写,可以省去大部分的寻址时间

kafka是这样做的,对于topic中的每个分区,他把从发送端收到的消息顺序的写到每一个log文件中,写满了之后再写入到下一个,就这样顺序的写下去,消费的时候也是从某一个位置开始,顺序消费

这样做极大的提高了磁盘IO性能

3.利用PageCache加速消息读写

减少磁盘IO开销

注意,是加速读写,PageCache是操作系统的一个特征,也就是操作系统给磁盘中的文件建立的缓存,那么对于读写两种情况分别讨论:

当调用一个写操作的系统调用时,不会直接把消息写到磁盘里面,而是将数据先写入到内存中的PageCache上面,然后再一批一批的写入到磁盘中去,这并不是kafka做了多大的优化,而是在设计时利用了操作系统先天的优越性,同时操作系统的这种写机制也是带有一种批处理的意思

当调用一个读操作的系统调用时,有两种情况:当可以命中PageCache时,那么直接进行读写,当没有命中时,那么就要进行读取磁盘的操作,而这个过程相比于内存操作是非常耗时的,应用程序的读取线程此时会被堵塞,一直等到操作系统将数据从磁盘中放到PageCache中,然后进行读取

一般应用程序在使用完PageCache中的数据之后,并不会立即清空,而是尽内存的努力最大程度的保存,如果实在不能保存就采用LRU或者其他变种算法出发内存淘汰机制,既然淘汰的是最近最少被使用的,那么逻辑就是保留最近最经常被使用的,那么这部分数据是什么呢?就是发送端刚刚写入到服务端的数据!这部分数据刚刚写入到服务端内存中的PageCache,当消费者进行读取的时候,命中的概率非常之高!

这样做不仅提高了消费者的读取能力,还间接的给服务端磁盘写入消息让出了磁盘IO,因为消费者不需要从磁盘中读数据,那么就可以增加写入磁盘IO的性能。

4.零拷贝技术

减少数据拷贝开销

当消费者进行读取数据时,服务端做了这些事情:

  • 将数据从磁盘文件中复制到PageCache中
  • 通过网络将消息发送给客户端

这个过程实际上进行了两次或者三次复制:

  • 将磁盘文件中的数据复制到PageCache中(如果在PageCache中可以命中,那么这一步可以省略)
  • 将PageCache中的数据复制到应用程序的内存空间中
  • 将应用程序内存中的数据复制到Socket缓冲区

上面所说的PageCache实际上就是内核缓冲区,应用程序的内存空间就是用户空间的缓冲区,而每创建一个Socket之后,都会分配两个缓冲区,分别是写入缓冲区和读取缓冲区

kafka使用到了零拷贝技术,将上面的第二次复制和第三次复制合并成一步,也就是直接将PageCache中的数据复制到Socket缓冲区中,注意这里的零拷贝并不是说一次数据复制都没有,我觉得这是相对于CPU来讲了,因为这种一次复制不会经过用户内存空间,那么就不需要CPU参与,而是可以通过DMA控制器完成数据的复制,所以相对于CPU来讲,确实是“零”

参考文章:玥哥极客时间《消息队列高手课》