Kafka高性能

张开发
2026/4/7 10:41:58 15 分钟阅读

分享文章

Kafka高性能
一.多层次Kafka本质是数组数组的优势在于存储连续方便索引如果顺序写入性能会高。但如果光是一个数组随着数据越积越多单一磁盘存放越来越吃力造成I/O压力过大。所以kafka不仅仅是一个数组实际上Kafka充分利用分治的思想将这个抽象的大数组划分为很多个小数组。kafka多层划分首先是Topic划分不同的Topic可以看作不同的小数组这些小数组可以分别存放在不同的Broker上其次每个Topic还做了切分分为了多个Partition也就是说把一个主题可以划分为多个主题分片。这些主题分片共同组成主题数组最后每个Partition还要进一步拆分一个partition实际对应了多个不同的文件这些文件是分离开的分为:.log文件即消息本身记录了数据.timeindex文件时间索引即可以通过时间对.log文件做索引查询.index文件即偏移量索引即可以通过偏移量对.log文件做索引查询显然.log就是数据其它两个文件是不同维度快速定位数据的索引。这样查找的时候就不是在.log文件中直接找而是先去找.index或者.timeindex这两个文件这两个文件是要加载到内核内存的所以不能太多。聊到这里其实基本已经清楚Kafka通过topic进行数据分片再通过Partition将Topic进行分片每个Partition分为三个文件其中利用(.index和.timeindex)索引文件可以实现(.log)数据的快速查找这个查找主要是给消费者提供支持。有同学可能会问最后如果某个Topic的某个Partition因为消息非常多Partition对应的3个文件不最终也会不堪重负吗?是的所以Kafka实际还做了进一步分片:按大小滚动单个文件达到阈值就分裂出一个新的文件继续写。Partition数据拆分成的多个小文件叫segment每个segment的大小可以通过log.segment.size 配置默认是1GB也就是说每1GB滚动分片一次。注意每个小文件也有自己的数据文件和索引文件索引文件包含偏移索引和时间索引。二.顺序写前面有讲过Kafka写入数据其实最终是写到每个Partition的末端也就是写入对应的磁盘文件中本节我们就介绍一下这个机制:顺序写磁盘我们都知道内存写入通常远快于磁盘写入但是也有例外也就是磁盘顺序写入的话性能和内存差距并不会太大所以落盘场景是可以考虑磁盘顺序写的。这个顺序写其实就是追加写每条消息都要紧跟在前一条后面。基于兼顾复杂度和性能的考虑Kafka的写入模式专门设计成了顺序写入这里要注意一点写磁盘也不一定是直接刷盘的只是说提交给了操作系统这里还是有丢失数据的可能性只是相对于先写Kafka应用程序内存已经是减少了一个可能遗失的环节了当然后面小节我们也会提到相关机制这里稍微了解下即可。顺序写入为什么这么快一般而言写磁盘的性能会远远低于操作内存但是顺序写入则不一样顺序写入的性能通常而言可以高出随机写入3个以上的数量级甚至接近内存写入。为什么顺序写入性能会这么高?为了解决这个问题我们需要先理解。写入磁盘具体是做什么?我们可以简单一点把写入磁盘分为两步:1.寻址;2.数据传输寻址需要磁头转动是机械操作是主要耗时的地方而随机写入就得每一次都去寻址这就意味着每一次都需要机械活动自然就非常曼所以从磁盘的视角来看它是很讨厌随机写入的。有个简单理解其实就差不多了如果想更深入一点理解可以把磁盘写入拆得更细致:磁头沿着半径机械移动最终移动到数据所在的磁柱盘片旋转是磁盘对齐数据所在扇区数据传输也就是写入数据12我们都可以看作寻址3是数据传输在随机读写情况下写100,000次数据就需要100,000次磁头移动时间、100,000次盘面旋转时间而顺序写入情况下只需要1次磁头移动和1次盘面旋转即一次寻址然后最后都是写100,000次数据但是大的耗时被缩减了所以顺序写入的性能自然就上来了。三.页缓存顺序写磁盘的速度已经很快了顺序写内存的话就更快Kafka利用操作系统自带的Page Cache来实现一定程度循序读写内存。Page Cache可以简单看作热点磁盘数据的内存缓存当消息写入时是先写入Page Cache,后面又操作系统将其刷入磁盘这样性能就提升了很多。同时如果查询时候发现PageCache中有对应数据那么也就不用去磁盘读取这样读取性能也会有很大的提升。值得一提的是Kafka是生产消费者模式即生产了消息在无积压情况下这个消息很快就会被消费也就是说我们生产消费时写入了Page Cache而很快就有消费者来触发Kafka应用程序读取对应数据而这个时间间隔很短PageCache命中的可能性会很高。Page Cache数据和磁盘同步数据写入Page Cache之后是需要和磁盘同步的这是因为如果电脑断电或者重启这部分数据就会丢失。数据同步有几个时机:当空间内存不够用了也就是说低于某个阈值时此时将PageCache刷入并释放Page Cache当脏页在内存驻留时间超过一个阈值时写入数据之后Page Cache会标记为脏页即内存数据页和磁盘页的内容不一致用户主动调用刷盘系统调用sync()和fsync(),这两个调用大概知道就行总之Page Cache 是提升Kafka性能的重要机制通过合理配置Kafka参。数和操作系统参数可以充分利用 Page Cache 提高消息的读写性能。了解 Page Cache的工作原理并进行适当的优化对于保障Kafka集群的高效运行至关重要。四.零拷贝详细文章可看:https://mp.weixin.qq.com/s/4ZTqvsLzg6-kJFJez4Zkqw常规传输为何低效如果应用程序要从磁盘读取数据发送到网络就会经历下图的流程:2次系统调用:一次是read()一次是一次是write()每次系统调用都得先从用户态切换到内核态等内核完成任务后再从内核态切换回用户态所以共发生了4次用户态与内核态的上下文切换。上下文切换到成本并不小一次切换需要耗时几十纳秒到几微秒虽然时间看上去很短但是在高并发的场景下这类时间容易被累积和放大从而影响系统的性能。4次数据拷贝: 还发生了4次数据拷贝其中两次是DMA的拷贝另外两次则是通过CPU拷贝的我们回过头看这个数据传输的过程我们只是搬运一份数据结果却搬运了4次过多的数据拷贝无疑会消耗CPU资源大大降低了系统性能。这种简单又传统的数据传输方式存在冗余的上文切换和数据拷贝在高并发系统里是非常糟糕的多了很多不必要的开销会严重影响系统性能。优化思路减少系统调用次数读取磁盘数据的时候之所以要发生上下文切换这是因为用户空间没有权限操作磁盘或者网卡内核的权限最高这些操作设备的过程都需要交给操作系统内核来完成所以一般要通过内核去完成某些任务的时候就需要使用操作系统提供的系统调用函数。而一次系统调用必然会发生2次上下文切换:首先从用户态切换到内核态当内核执行完任务后再切换回用户态交由进程代码执行。所以要想减少上下文切换的次数就要减少系统调用的次数。减少数据拷贝次数再来看看如何减少「数据拷贝」的次数?在前面我们知道了传统的文件传输方式会历经4次数据拷贝而且这里面「从内核的读缓冲区拷贝到用户的缓冲区里再从用户的缓冲区里拷贝到socket的缓冲区里」这个过程是没有必要的。因为文件传输的应用场景中在用户空间我们并不会对数据「再加工」所以数据实际上可以不用搬运到用户空间因此用户的缓冲区是没有必要存在的。那么有没有一种技术可以是实现数据之间在核心态进行传输而不需要将数据在核心态和用户态之间来回复制最终发送给接收端呢?答案是肯定的下面我们来简单介绍一下零拷贝技术。零拷贝技术所谓的零拷贝是指将数据在内核空间直接从磁盘文件复制到网卡中而不需要经由用户态的应用程序之手。这样既可以提高数据读取的性能也能减少核心态和用户态之间的上下文切换提高数据传输效率。具体流程如下:sendfile开启流程可以看到sendfile是代替了read和write相当于只有一次系统调用了操作系统将数据从磁盘通过DMA加载到内核空间的缓存区操作系统将数据的描述符拷贝到Socket缓冲区中。Socket缓冲区仅仅会拷贝一个描述符过去不会拷贝数据。操作系统直接将数据从内核空间的缓存区传输到网卡中并通过网卡将数据发送给接收方这也是通过DMA来做的不过这里的DMA叫SG-DMA基本上现阶段的网卡都是支持SG-DMA的所以没有什么特别的知道它是一种加强的DMA技术就行了甚至面试时候就说是DMA也是一样的。从上面流程我们可以看到使用零拷贝之后系统调用次数从2次变成了1次拷贝次数从4次变成了2次显著减少了数据传输的损耗。零拷贝是实现Kafka高性能的其中一种手段涉及到从磁盘到网卡的数据传输操作都可以使用零拷贝这项技术来进行优化以提升性能五.批量操作Kafka主要有两个批量操作的地方一个是批量生产也就是批量发送其实就是通过发送缓冲将数据缓冲起来等聚集了一批数据再一次性发送给Broker另一个就是批量消费本质就是一次多拉几条一起消费。要注意批量生产和批量消费不是成对关系是相互独立的优化手段。六.数据压缩生产者通常发送基于文本的数据例如JSON数据。在这种情况下对生产者应用压缩非常重要。如果开启了压缩生产者消息以压缩的方式发送待消费时再解压。Kafka支持两种类型的压缩producer端和broker端。那么什么时候需要压缩呢?如果追求高性能的服务那么正常来说是需要开启压缩的。软件领域没有银弹数据压缩也是有代价的它会付出额外的CPU你需要有这么一个判断:压缩带来的磁盘、带宽节省的收益是大于CPU一定程度的损失的。那怎么判断收益是否大于损失呢?这个得看业务团队的具体情况来分析:如果团队CPU资源多到用不完而瓶颈在于带宽或者磁盘存储那么显然是要开启压缩的如果消息比较大可以考虑压缩消息自身内容如果重复度高可以考虑压缩压缩的本质就是针对重复片段用简单代码取代以实现让数据更小的目的所以重复度越高压缩效果肯定是越好的在Producer端进行消息压缩producer可以选择使用该compression.type设置来压缩消息这个参数可以设置为none, gzip, lz4, snappy和zstd(注意:zstd压缩是在Kafka 2.1之后引入的)等压缩算法考虑测试 snappy 或lz4以获得最佳速度/压缩比注意此时压缩操作是由producer来进行和broker无关也就不需要broker更改任何配置。另外为了进一步提高性能我们还可以可以结合上一节消息批量发送的优化一起因为一批消息一起压缩则单个生产者批次中的所有消息将被压缩在一起并作为一个整体行的压缩消息发送这样压缩效果肯定会更好进而吞吐量也就更高。在Broker端进行消息压缩broker也可以进行压缩即在发送端不进行压缩这样享受不了发送环节的性能增长但还是可以享受更高磁盘利用率。Broker压缩是主题级的也就是说不同主题可以有不同配置包括用不同的压缩算法。默认情况下broker是不开启压缩的体现在配置上就是主题压缩定义为compression.typeproducer 即即直接继承producer 端所发来消息的压绎方式即无论是否压缩无论采用哪种压缩算法broker都原样存储消息。如果Broker开启了压缩而Producer未开启压缩那么没有歧义就在Broker执行压缩即可。但是如果Broker和Producer端同时开启了压缩配置那么有如下规则:如果Broker端和Producer端的压缩设置是一样的(例如1z4)那么Broker不会进行重复压缩消息批次将按原样写入日志文件如果Broker端和Producer端的压缩设置是不一样的那么Broker将解压缩消息并将其重新压缩为其配置的格式。

更多文章