kafka设计的目标之一就是高吞吐量。除了最基础的将一个topic划分为多个partition外,还从以下各个方面优化。

kafka broker端为了提高吞吐量:实现顺序读写磁盘、利用page cache,将文件数据映射到内存,利用sendfile网传时socket通信时直接读取内存区域(减少操作系统上下文切换、零拷贝提速);

producer端,将消息buffer起来,当消息的条数达到一定阀值时(一定数量或时间),批量发送给broker;

consumer,批量fetch多条消息.通过配置到达一定阈值时(一定数量或时间),批量从broker拉取信息;

对于producer/consumer/broker三者而言,CPU的开支应该都不大,因此启用消息压缩机制减少网传数据量;压缩需要消耗少量的CPU资源,可以将任何在网络上传输的消息都经过压缩.kafka支持gzip/snappy等多种压缩方式。

顺序写磁盘

根据《一些场景下顺序写磁盘快于随机写内存》所述,将写磁盘的过程变为顺序写,可极大提高对磁盘的利用率。

Kafka的整个设计中,Partition相当于一个非常长的数组,而Broker接收到的所有消息顺序写入这个大数组中。同时Consumer通过Offset顺序消费这些数据,并且不删除已经消费的数据,从而避免了随机写磁盘的过程。

由于磁盘有限,不可能保存所有数据,实际上作为消息系统Kafka也没必要保存所有数据,需要删除旧的数据。而这个删除过程,并非通过使用“读-写”模式去修改文件,而是将Partition分为多个Segment,每个Segment对应一个物理文件,通过删除整个文件的方式去删除Partition内的数据。这种方式清除旧数据的方式,也避免了对文件的随机写操作。

通过如下代码可知,Kafka删除Segment的方式,是直接删除Segment对应的整个log文件和整个index文件而非删除文件中的部分内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Delete this log segment from the filesystem.
*
* @throws KafkaStorageException if the delete fails.
*/
def delete() {
val deletedLog = log.delete()
val deletedIndex = index.delete()
val deletedTimeIndex = timeIndex.delete()
if(!deletedLog && log.file.exists)
throw new KafkaStorageException("Delete of log " + log.file.getName + " failed.")
if(!deletedIndex && index.file.exists)
throw new KafkaStorageException("Delete of index " + index.file.getName + " failed.")
if(!deletedTimeIndex && timeIndex.file.exists)
throw new KafkaStorageException("Delete of time index " + timeIndex.file.getName + " failed.")
}

充分利用Page Cache

使用Page Cache的好处如下

  • I/O Scheduler会将连续的小块写组装成大块的物理写从而提高性能
  • I/O Scheduler会尝试将一些写操作重新按顺序排好,从而减少磁盘头的移动时间
  • 充分利用所有空闲内存(非JVM内存)。如果使用应用层Cache(即JVM堆内存),会增加GC负担
  • 读操作可直接在Page Cache内进行。如果消费和生产速度相当,甚至不需要通过物理磁盘(直接通过Page Cache)交换数据
  • 如果进程重启,JVM内的Cache会失效,但Page Cache仍然可用

Broker收到数据后,写磁盘时只是将数据写入Page Cache,并不保证数据一定完全写入磁盘。从这一点看,可能会造成机器宕机时,Page Cache内的数据未写入磁盘从而造成数据丢失。但是这种丢失只发生在机器断电等造成操作系统不工作的场景,而这种场景完全可以由Kafka层面的Replication机制去解决。如果为了保证这种情况下数据不丢失而强制将Page Cache中的数据Flush到磁盘,反而会降低性能。也正因如此,Kafka虽然提供了flush.messagesflush.ms两个参数将Page Cache中的数据强制Flush到磁盘,但是Kafka并不建议使用。

如果数据消费速度与生产速度相当,甚至不需要通过物理磁盘交换数据,而是直接通过Page Cache交换数据。同时,Follower从Leader Fetch数据时,也可通过Page Cache完成。下图为某Partition的Leader节点的网络/磁盘读写信息。

从上图可以看到,该Broker每秒通过网络从Producer接收约35MB数据,虽然有Follower从该Broker Fetch数据,但是该Broker基本无读磁盘。这是因为该Broker直接从Page Cache中将数据取出返回给了Follower。

支持多Disk Drive

Broker的log.dirs配置项,允许配置多个文件夹。如果机器上有多个Disk Drive,可将不同的Disk挂载到不同的目录,然后将这些目录都配置到log.dirs里。Kafka会尽可能将不同的Partition分配到不同的目录,也即不同的Disk上,从而充分利用了多Disk的优势。

零拷贝

Kafka中存在大量的网络数据持久化到磁盘(Producer到Broker)和磁盘文件通过网络发送(Broker到Consumer)的过程。这一过程的性能直接影响Kafka的整体吞吐量。

传统模式下的四次拷贝与四次上下文切换

以将磁盘文件通过网络发送为例。传统模式下,一般使用如下伪代码所示的方法先将文件数据读入内存,然后通过Socket将内存中的数据发送出去。

1
2
buffer = File.read
Socket.send(buffer)

这一过程实际上发生了四次数据拷贝。首先通过系统调用将文件数据读入到内核态Buffer(DMA拷贝),然后应用程序将内存态Buffer数据读入到用户态Buffer(CPU拷贝),接着用户程序通过Socket发送数据时将用户态Buffer数据拷贝到内核态Buffer(CPU拷贝),最后通过DMA拷贝将数据拷贝到NIC Buffer。同时,还伴随着四次上下文切换,如下图所示。

sendfile和transferTo实现零拷贝

Linux 2.4+内核通过sendfile系统调用,提供了零拷贝。数据通过DMA拷贝到内核态Buffer后,直接通过DMA拷贝到NIC Buffer,无需CPU拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件-网络发送由一个sendfile调用完成,整个过程只有两次上下文切换,因此大大提高了性能。零拷贝过程如下图所示。

从具体实现来看,Kafka的数据传输通过TransportLayer来完成,其子类PlaintextTransportLayer通过Java NIO的FileChannel的transferTotransferFrom方法实现零拷贝,如下所示。

1
2
3
4
@Override
public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
return fileChannel.transferTo(position, count, socketChannel);
}

注: transferTotransferFrom并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供sendfile这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。

减少网络开销

批处理

批处理是一种常用的用于提高I/O性能的方式。对Kafka而言,批处理既减少了网络传输的Overhead,又提高了写磁盘的效率。

Kafka 0.8.1及以前的Producer区分同步Producer和异步Producer。同步Producer的send方法主要分两种形式。一种是接受一个KeyedMessage作为参数,一次发送一条消息。另一种是接受一批KeyedMessage作为参数,一次性发送多条消息。而对于异步发送而言,无论是使用哪个send方法,实现上都不会立即将消息发送给Broker,而是先存到内部的队列中,直到消息条数达到阈值或者达到指定的Timeout才真正的将消息发送出去,从而实现了消息的批量发送。

Kafka 0.8.2开始支持新的Producer API,将同步Producer和异步Producer结合。虽然从send接口来看,一次只能发送一个ProducerRecord,而不能像之前版本的send方法一样接受消息列表,但是send方法并非立即将消息发送出去,而是通过batch.sizelinger.ms控制实际发送频率,从而实现批量发送。

由于每次网络传输,除了传输消息本身以外,还要传输非常多的网络协议本身的一些内容(称为Overhead),所以将多条消息合并到一起传输,可有效减少网络传输的Overhead,进而提高了传输效率。

零拷贝章节的图中可以看到,虽然Broker持续从网络接收数据,但是写磁盘并非每秒都在发生,而是间隔一段时间写一次磁盘,并且每次写磁盘的数据量都非常大(最高达到718MB/S)。

数据压缩降低网络负载

Kafka从0.7开始,即支持将数据压缩后再传输给Broker。除了可以将每条消息单独压缩然后传输外,Kafka还支持在批量发送时,将整个Batch的消息一起压缩后传输。数据压缩的一个基本原理是,重复数据越多压缩效果越好。因此将整个Batch的数据一起压缩能更大幅度减小数据量,从而更大程度提高网络传输效率。

Broker接收消息后,并不直接解压缩,而是直接将消息以压缩后的形式持久化到磁盘。Consumer Fetch到数据后再解压缩。因此Kafka的压缩不仅减少了Producer到Broker的网络传输负载,同时也降低了Broker磁盘操作的负载,也降低了Consumer与Broker间的网络传输量,从而极大得提高了传输效率,提高了吞吐量。

高效的序列化方式

Kafka消息的Key和Payload(或者说Value)的类型可自定义,只需同时提供相应的序列化器和反序列化器即可。因此用户可以通过使用快速且紧凑的序列化-反序列化方式(如Avro,Protocal Buffer)来减少实际网络传输和磁盘存储的数据规模,从而提高吞吐率。这里要注意,如果使用的序列化方法太慢,即使压缩比非常高,最终的效率也不一定高。Kafka设计解析(六)- Kafka高性能架构之道

kafka设计要点之高吞吐量的更多相关文章

  1. kafka 基础知识梳理-kafka是一种高吞吐量的分布式发布订阅消息系统

    一.kafka 简介 今社会各种应用系统诸如商业.社交.搜索.浏览等像信息工厂一样不断的生产出各种信息,在大数据时代,我们面临如下几个挑战: 如何收集这些巨大的信息 如何分析它 如何及时做到如上两点 ...

  2. Kafka如何保证高吞吐量

    1.顺序读写 kafka的消息是不断追加到文件中的,这个特性使kafka可以充分利用磁盘的顺序读写性能 顺序读写不需要硬盘磁头的寻道时间,只需很少的扇区旋转时间,所以速度远快于随机读写 生产者负责写入 ...

  3. Kafka 设计与原理详解

    一.Kafka简介 本文综合了我之前写的kafka相关文章,可作为一个全面了解学习kafka的培训学习资料. 转载请注明出处 : 本文链接 1.1 背景历史 当今社会各种应用系统诸如商业.社交.搜索. ...

  4. kafka之二:Kafka 设计与原理详解

    一.Kafka简介 本文综合了我之前写的kafka相关文章,可作为一个全面了解学习kafka的培训学习资料. 转载请注明出处 : 本文链接 1.1 背景历史 当今社会各种应用系统诸如商业.社交.搜索. ...

  5. kafk设计要点

    kafka的设计目标是高吞吐量,所以kafka自己设计了一套高性能但是不通用的协议,他是仿照AMQP( Advanced Message Queuing Protocol   高级消息队列协议)设计的 ...

  6. 高吞吐量的分布式发布订阅消息系统Kafka--安装及测试

    一.Kafka概述 Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据. 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因 ...

  7. 高吞吐量消息系统—kafka

    现在基本上大数据的场景中都会有kafka的身影,那么为什么这些场景下要用kafka而不用其他传统的消息队列呢?例如rabbitmq.主要的原因是因为kafka天然的百万级TPS,以及它对接其他大数据组 ...

  8. kafka高吞吐量的分布式发布订阅的消息队列系统

    一:kafka介绍kafka(官网地址:http://kafka.apache.org)是一种高吞吐量的分布式发布订阅的消息队列系统,具有高性能和高吞吐率. 1.1 术语介绍BrokerKafka集群 ...

  9. kafka高吞吐量之消息压缩

    背景 保证kafka高吞吐量的另外一大利器就是消息压缩.就像上图中的压缩饼干. 压缩即空间换时间,通过空间的压缩带来速度的提升,即通过少量的cpu消耗来减少磁盘和网络传输的io. 消息压缩模型 消息格 ...

随机推荐

  1. List/Map 导出到表格(使用注解和反射)

    Java 的 POI 库可以用来创建和操作 Excel 表格,有时候我们只需要简单地将 List 或 Map 导出到表格,样板代码比较多,不够优雅.如果能像 Gson 那样,使用注解标记要导出的属性, ...

  2. HashSet去重

    class Program     {         static void Main(string[] args)         {             Console.WriteLine( ...

  3. TRIO-basic指令--CAM

    大家好,今天更新TRIO的运动指令CAM(也就是CAM函数),CAM指令是控制器直接发送编码器脉冲形成的运动曲线,比如:正弦,余弦曲线,根据自己的精度需求进行描点,但并不一定点数越多精度就越高,以实际 ...

  4. 手机端 关闭当前页面的JS

    JS 代码如下   PS:我找的原代码中, 注释部分是没有注释的,但是调试的时候报错,就注释掉了,因为我只需要微信和支付宝的判定,所以这个地方,就没仔细研究 var isLppzApp = false ...

  5. 类再生(合成、继承、final)

    类再生 有两种方法达到代码复用的效果:合成.继承. 合成的语法 合成就是形成对象,把复用的代码置入对象句柄. 在类内字段使用基本数据会初始化为零,但对象句柄会初始化为null.在下面的程序中若没有ne ...

  6. Python删除列表元素的3种方法

    之前看教程的时候比较着急,对这些基础掌握不好,过来回顾一下 使用del语句删除 lis = [1, 2, 3, 'a', 'b'] print(lis) del lis[0] print(lis) 输 ...

  7. Java自学-数字与字符串 字符串

    Java中的字符串String 示例 1 : 创建字符串 字符串即字符的组合,在Java中,字符串是一个类,所以我们见到的字符串都是对象 常见创建字符串手段: 每当有一个字面值出现的时候,虚拟机就会创 ...

  8. Tomcat配置https加密连接

    配置https安全连接(ssl加密连接) https连接需要用到数字证书与数字签名(MD5算法),网站https连接首先需要申请数字证书,配置加密连接器,浏览器安装证书. 证书运用到RSA技术,RSA ...

  9. jquery问题,如何调用带this的函数?

    这样写: 1 2 3 4 5 6 7 8 9 10 11 12 $(".aa").on("mouseout",function(){     var obj = ...

  10. select用法 多并发处理

    select默认最大检查套接口数量是1024,有定义 #define __NFDBITS (8 * sizeof(unsigned long)) #define __FD_SETSIZE 1024 # ...