Kafka高性能原理

Kafka线程模型

1(Acceptor) + N(Processor) + M(KafkaRequestHandler),在Netty,Tomcat,Nginx上面都能看见类似的设计

N = num.networker.threads

M = num.io.threads

一个EndPoint(网卡)对于一个Acceptor,一般来说就一个

kafka存储模型

日志文件

Kafka节点上,一个Partition对应一个磁盘目录,命名为_,分为多个LogSegment,一个LogSegment,一个LogSegment对应磁盘上一个日志文件和一个索引文件,日志文件命名规则为[baseOffset].log,baseOffset是日志文件中第一条消息的offset。

写入时数据直接append到文件末尾,所以不管文件多大,写入总是O(1)的时间复杂度。

索引文件

索引是分段和稀疏索引的方式,二分查找定位日志位点,返回低位点。和日志不同,索引文件因为比较小,用mmap的方式操作,速度很快。

Kafka高性能原理

说了半天终于到最核心的地方了,也就是Kafka为什么这么快!

页缓存

Kafka并不太依赖JVM内存大小,而是主要利用Page Cache,如果使用应用层缓存(JVM堆内存),会增加GC负担,增加停顿时间和延迟,创建对象的开销也会比较高。

读取操作可以直接在Page Cache上进行,如果消费和生产速度相当,甚至不需要通过物理磁盘直接交换数据,这是Kafka高吞吐量的一个重要原因。

这么做还有一个优势,如果Kafka重启,JVM内的Cache会失效,Page Cache依然可用。

零拷贝

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

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

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

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方法实现零拷贝。

@Overridepublic long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
    return fileChannel.transferTo(position, count, socketChannel);
}

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

磁盘顺序写入

即使是普通的机械磁盘,顺序访问速率也接近了内存的随机访问速率。

Kafka的每条消息都是append的,不会从中间写入和删除消息,保证了磁盘的顺序访问。

即使是顺序读写,过于频繁的大量小IO操作一样会造成磁盘的瓶颈,此时又变成了随机读写。Kafka的策略是把消息集合在一起,批量发送,尽可能减少对磁盘的访问。所以,Kafka的Topic和Partition数量不宜过多,可以看阿里云中间件团队的这篇文章:Kafka vs RocketMQ——多Topic对性能稳定性的影响,超过64个Topic/Partition以后,Kafka性能会急剧下降。

RocketMQ存储模型与Kafka有些差异,RocketMQ会把所有的数据存放在相同的日志文件,所以单机可以支持非常多的队列。

全异步

Kafka基本上是没有阻塞操作的,调用发送方法会立即返回,等待buffer满了以后交给轮询线程,发送和接收消息,复制数据也是都是通过NetworkClient封装的poll方式。

批量操作

结合磁盘顺序写入,批量无疑是非常有必要(如果用的时候每发送一条消息都调用future.get等待,性能至少下降2个数量级)。写入的时候放到RecordAccumulator进行聚合,批量压缩,还有批量刷盘等...

精妙的组件设计

Kafka里有不少精妙的设计,比如说DelayedOperationPurgatory和多层TimingWheel,保证了大量延迟任务的高性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值