一、Producer端消息优化

Kafka支持使用异步批量的方式发送消息。当Producer生产一条消息时,并不会立刻发送到Broker,而是先放入到消息缓冲区,等到缓冲区满或者消息个数达到限制后,再批量发送到Broker。Producer端需要注意以下参数:

kafka集群的吞吐量 kafka吞吐量怎么计算_kafka

  • acks参数:**表示Producer发送消息后是否需要等待broker的应答。目前提供三个取值,acks=0 表示发送消息后立即返回,不需要等待broker的确认;acks=1 表示消息被写入到主分区后,broker需给予应答,此时并不保证已写入kafka的复制分区,如果主分区挂掉,消息可能会丢失;acks=-1 是最严格的确认,必须等到消息写入到主分区并且同步复制分区成功后才会应答。
  • buffer.memory参数:表示消息缓存区的大小,单位是字节。
  • batch.size参数:batch的阈值。当kafka采用异步方式发送消息时,默认是按照batch模式发送。其中同一主题同一分区的消息会默认合并到一个batch内,当达到阈值后就会发送。
  • linger.ms参数:这个是设置消息发送延迟,这样可以收集更多的消息后批量发送,默认大小是0ms(就是有消息就立即发送)。

通过上述参数可以做可靠性、时延和吞吐量之间的权衡。如果业务对消息丢失有一定容忍,对时延要求不高,例如点击率统计,可以设置较大的batch.size,较长的linger.ms,并且设置acks=0,这样可以达到很高的吞吐率。

二、磁盘顺序读写

Kafka本质上是基于磁盘存储的消息队列,虽然在我们固有印象中磁盘的读写速度是非常慢的,但是Kafka巧妙利用了操作系统对IO的优化策略,达到了和内存读写近似的效果。这种优化策略就是磁盘缓存(Page Cache)。Page Cache可以看作磁盘文件在物理内存中的映射,根据局部性原理,如果某块数据被用到,那么存储在它周围的数据也很可能即将被用到。操作系统可以采用预读和后写的方式,对磁盘读写进行优化。

  • 预读:磁盘顺序读取的效率是很高的(不需要寻道时间,只需要很少的旋转时间)。而在读取磁盘某块数据时,同时会顺序读取相邻地址的数据加载到PageCache,这样在读取后续连续数据时,只需要从PageCache中读取数据,相当于内存读写,速度会特别快。
  • 后写:数据并不是直接写入到磁盘,而是默认先写入到Page Cache,再由Page Cache刷新到磁盘,刷新频率是由操作系统周期性的sync触发的(用户也可以手动调用sync触发刷新操作)。后写的方式大大减少对磁盘的总写入次数,提高写入效率。

Kafka中的消息存储在partition中,每个partition对应一组物理空间连续的磁盘文件。当有新消息进来时,会以追加的方式写入到磁盘文件末尾。消费者拉取消息时,也是以partition为单位,顺序拉取数据消费。可以看出来Kafka的读写都是顺序的,可以很高效地利用PageCache,解决磁盘读写的性能问题。

三、零拷贝技术

零拷贝技术一种是对IO的进一步优化,它的原理是通过减少数据在内存中的拷贝次数,来提高IO性能。从Broker的角度考虑,Kafka的消费对应是从磁盘文件读取数据发送到网卡的过程,介绍零拷贝之前,我们先看如果用传统IO,这个过程是怎么实现的:

kafka集群的吞吐量 kafka吞吐量怎么计算_数据_02

  • 应用进程使用系统调用read(),从用户态切换到内核态;
  • 操作系统委托DMA(Direct Memory Access 存储器直接访问)将硬盘数据copy到内核缓冲区(DMA copy);
  • read()返回,内核缓存区数据拷贝到用户缓冲区(CPU copy),同时进程从内核态切换到用户态;
  • 应用进程取到数据后,进行系统调用write(),再次从用户态切换到内核态;
  • 数据从用户缓冲区拷贝到socket缓冲区(CPU copy);
  • 操作系统委托DMA将socket缓冲区数据写入到网卡(DMA copy);
  • write()方法返回,进程从内核态切换到用户态,整个流程结束。

传统过程主要依赖于read/write系统调用,一共需要4次上下文切换,2次CPU copy,2次DMA copy,其中上下文切换和CPU copy都要消耗大量系统资源(DMA copy无需CPU介入,相对不消耗资源)。那能否优化这个过程?

Kafka使用sendfile的系统调用代替传统的read/write系统调用。sendfile是非常典型的零拷贝技术,我们再看sendfile是怎么实现磁盘文件读取数据发送到网卡的这个过程:

kafka集群的吞吐量 kafka吞吐量怎么计算_java_03

  • 应用进程进行系统调用sendfile,从用户态进入内核态;
  • 操作系统委托DMA将硬盘数据拷贝到内核缓冲区(DMA copy);
  • 操作系统将文件描述符和数据长度发送到socket缓冲区,同时委托DMA拷贝数据,直接从内核缓冲区将数据写入到网卡;
  • sendfile方法返回,进程从内核态切换到用户态,流程结束。

可见使用sendfile流程后,一共只需要两次上下文切换,0次CPU copy,性能上的提升很明显。

四、End-To-End的压缩方式

在带宽受限的情况下,吞吐量和消息的大小呈反比关系,所以压缩消息能显著提升吞吐量。Kafka支持多种压缩算法,例如GZIP、Snappy和LZ4和Zstd。同时Kafka支持End-To-End的压缩方式,消息在生产端被压缩,Broker端默认不解压,直接存储到磁盘,直到消费端才被解压,这种方式保证了消息在网络传输过程中一直是被压缩的状态。