刚从网易二面的会议室出来,脑子还嗡嗡的,不是因为被面得难受,而是一道题让我觉得聊得特别痛快。面试官问的是:“Kafka为什么吞吐量大、速度快?”
这问题在Kafka面试题里属于必考题,但我发现很多人回答得都挺浅的,张嘴就是“顺序写、零拷贝”,然后就没有然后了。这两个词确实是核心,但它们只是整个故事的一半。真正要让面试官点头,得把一个“消息从生产者产生,到消费者拿到”的完整链条讲清楚,讲明白每一环是怎么为吞吐量服务的。
这篇文章我就把这个题目彻底拆开,从架构设计到存储原理,从批量压缩到参数调优,把Kafka高吞吐的底牌一张张翻给你看。这不是干巴巴背答案,而是把“为什么”三个字掰碎了讲透。不管你是准备面试,还是日常用Kafka但一直没深究过,读完之后你再去摸Kafka,感觉会完全不一样。
1. 面试官到底想考什么
如果你把“为什么快”当成一道填空题,那答完“顺序写、零拷贝”基本就凉了。面试官追问两句“然后呢”“还有吗”,你就开始冒汗。实际上这道题考察的是两件事:你的结构化表达能力,以及你对Kafka设计哲学的理解深度。
1.1 这道题背后的真实意图
先想想面试官问这个问题时在评估什么。他不可能真需要你教他Kafka为什么快,他是在看你怎么拆解一个复杂系统的问题。一个优秀的工程师遇到“某系统性能为什么好”这类问题,第一反应不应该是去背特性清单,而应该思考:这个系统面对的核心约束是什么?为了突破这个约束,它做了哪些核心设计决策?
Kafka面对的约束很明确:磁盘IO是慢速设备,网络IO有吞吐上限,传统消息队列逐条同步发送的效率太低。Kafka的高吞吐本质是围绕“顺序访问”“批量处理”“并行分治”“零拷贝”这四个词做了一系列取舍。
所以说这道题目的考察点包括:
- 是否了解Kafka的整体架构,而不仅是某个组件
- 能否分清“吞吐量的来源”和“延迟低的来源”,两者有交集但不是一回事
- 是否理解参数设计背后的原理,而不是只会背配置项
- 有没有实践层面调优的直觉,比如知道batch调大和flush调频分别带来什么
这也解释了为什么很多人准备了一堆Kafka知识点,真被问到时仍然讲不清楚,因为那些知识点是零散的。零散的碎片在面试高压下很难组织成有说服力的回答。
1.2 很多人回答这道题的通病
我在带团队面人的时候,听到太多类似回答了:
因为Kafka是分布式的,有分区,能并行处理,所以快。然后呢?没有然后了。
Kafka用了零拷贝技术,不走用户态和内核态,所以快。但再问一句“零拷贝具体省掉了哪几次拷贝”就答不上来了。
Kafka是顺序写磁盘的,所以快。然后被反问“顺序写比随机写快多少?为什么快?”开始含糊其辞。
最典型的通病就是:知道几个术语,却说不出这些术语背后的机制和数值概念。比如顺序写确实快,但如果你不知道HDD顺序写能到150MB/s往上的道理,不知道NTFS、ext4这类文件系统下随机写和顺序写的数量级差距,回答就没有说服力。
真正的回答应该像剥洋葱,一层层往里走:先用架构视角说Kafka怎么让数据并行流动起来,再落到单节点怎么把磁盘和网络用到极致,最后落到参数层怎么把性能调到最优。下面我就按这个逻辑,把通往高吞吐的每一层都展开说清楚。
2. 架构层:并行与分治是吞吐量的第一桶金
很多人的第一反应是“Kafka是分布式的”,这个起点没错,但需要延伸。分布式本身不直接等于高吞吐,让分布式真正生效的是数据被切分成可独立处理的分区,每个分区都能并行读写,就像一条高速公路增加车道。
2.1 分区机制如何撑起并行读写
Kafka把每个主题按分区切割。假设一个主题有12个分区,分布在3台broker上,那每台broker只需要处理4个分区的数据。写入时,生产者可以把消息同时发到12个分区;消费时,消费组里的消费者可以各自接管不同的分区。
这里面有个很多人没意识到的点:分区不只是存储维度的切分,更是并发维度的切分。每个分区对应一个活跃的日志段文件,broker的IO线程池可以并发地append不同的分区文件;消费者端,每个分区的消费是完全独立的,一个消费者处理不过来,加消费者、加分区就能水平扩展。这跟MySQL主库单点写形成鲜明对比,Kafka天生就是“多车道”架构。
但也正因为分区数量直接决定并发上限,部署时就要提前规划。我曾见过一个业务用3个分区扛每秒5万消息的流量,消费者怎么加线程都顶不住,最后把分区扩到24个才稳下来。所以这条“车道”必须在建主题时就留足余量:分区数建议至少按照“预估峰值吞吐/单分区可承受吞吐”的两倍来设计。
2.2 生产者端:异步缓冲让网络IO变成流水线
架构层面的下一个重点在生产端,这里藏着Kafka吞吐量远超大部分消息队列的细节。生产者发送消息不是一条一条现发,而是往本地的缓冲池里丢,后台的Sender线程按批次往外发,这是一条完整的流水线。
Kafka生产端有两个关键组件:RecordAccumulator和Sender线程。RecordAccumulator会按分区维护多个Deque<ProducerBatch>,每条消息先append到一个批次里,满了或者到了linger.ms时间就交给Sender发出去。这种设计最大的价值是把N条消息的网络往返,压缩成N/batchSize次请求。
举个例子,如果你的业务每秒产生5万条100字节的消息,每条单独发意味着每秒5万个请求,网卡和broker都要被请求风暴打蒙;但如果你把它们攒成每个32KB的批次,一批能装300条左右,每秒请求数直接降到170个左右。这就是“批量”两个字在设计层面带来的数量级提升。
另外,Kafka生产端还有一套很有意思的“双缓冲”思想。Sender线程在发一批数据时,生产者主线程还可以继续向另一批数据写入,互不阻塞。类比一下,就像餐厅传菜窗口:传菜员端走一批菜去上桌,后厨同时往窗口放下一批菜,两边都不用停下来等对方。
2.3 消费端:拉模式、分区独立、大数据批拉取
消费端的架构设计也为吞吐量做了大量优化。Kafka采用pull模式,消费者主动按照自己处理能力的节奏去拉数据,而不是broker把数据“推”给你。这在消费者处理速度慢于生产速度时非常重要:推模式下你来不及处理就会被压垮,拉模式下你可以先不拉或者少拉,系统永远是稳的。
消费端的高吞吐还体现在单次拉取的“量”上。Kafka允许消费者通过fetch.min.bytes和fetch.max.wait.ms控制一次拉取至少攒够多少字节才返回。实际业务里,把fetch.min.bytes调成1MB左右,一个消费者一次就能拉回上万条消息,再配合多线程按分区消费,单消费者的吞吐能做到几十万条/秒,这个量级在很多消息中间件里很难想象。
2.4 副本机制设计了宁可退,也不硬等
讲到架构层有人会问:Kafka是分布式的,有副本机制,副本同步难道不拖累写入性能吗?答案是Kafka在这块做了非常务实的设计:写入只需要ISR集合中的副本确认,而不是等全部分区副本都同步完成。
ISR(In-Sync Replicas)是Kafka运维必须理解的概念。它表示与leader保持同步的副本集合。当你设置acks=all时,leader只要等到ISR里所有副本都写成功就能返回ack。这里面有两点值得琢磨:
- 如果ISR里的follower副本因为网络或磁盘原因长时间追不上leader,它会被踢出ISR。ISR缩小后,写请求等待的确认节点变少,写入性能不会无限下跌。
- 实际上多数场景下
acks=all的吞吐损耗远小于直觉,因为follower的同步是follower主动拉取,leader把数据交给磁盘后就继续服务新请求,不会傻等。
很多团队追求数据安全,又把Topic的replication.factor设置成3,同时把min.insync.replicas设成2,这时写入至少等两个节点确认。实测在千兆网络下,相比单副本,吞吐损耗通常能控制在30%以内,这对于数据安全来说是值得的。所以说Kafka并没有因为副本机制而丢掉吞吐量,它只是把代价控制在一个工程上可接受的程度。
3. 存储层:Kafka速度的“核反应堆”
如果说架构层决定了Kafka吞吐量的上限天花板,那存储层就是支撑它逼近这个天花板的物理底座。这层包含的“顺序写”“页缓存”“零拷贝”“稀疏索引”,才是很多人面试时挂在嘴边却讲不透的核心。我一个个来拆。
3.1 抛弃随机写,改用顺序追加写
磁盘性能有个残酷的差异:随机写和顺序写的速度能差两到三个数量级。普通HDD随机写IOPS只有100出头,每秒写几百个小文件就顶天了;而HDD顺序写可以轻松跑到150MB/s甚至更高。SSD上两者的差距缩小了,但顺序写依然明显占优。Kafka选择了所有数据追加写日志的方式,不修改、不删除已经落盘的消息,彻底绕开了随机写。
有人会担心:顺序追加会不会让日志无限膨胀?Kafka用分段策略解决这个问题。每个Topic分区的数据按log.segment.bytes(默认1GB)切分成一个个日志段,写满一个就滚动下一个,同时定期清理过期段。这样“只追加不修改”的方式永远只对当前活跃段顺序写,历史段过期直接整体删除,不会产生碎片。
这里还有一个容易被人忽视的IO特征:顺序写让操作系统和磁盘的预读、写缓存都能发挥最大效用。磁盘控制器、文件系统、页缓存对连续IO的优化效果远好于随机IO,Kafka等于搭了一辆顺风车。
3.2 Page Cache:把整个磁盘IO链路从“慢”变“快”
Kafka另一个很大的设计决定是:不使用Linux的Direct IO,而是依赖操作系统的Page Cache。这个决定让很多人困惑,因为很多数据库都在强调绕过OS缓存自己管理。Kafka的底气在于它的数据形态极其适合页缓存。
从生产者角度看,消息是被追加到日志的。追加后的路径是:生产者数据 -> socket接收缓冲 -> 应用内存 -> 页缓存 -> 磁盘。当写入量还没超过页缓存容量时,写入数据实际上先落在内存页里,由操作系统后台异步刷盘。这就让Kafka面临“写”的时候,大部分IO走的是内存速度。
更妙的是,同一份页缓存可以被消费者直接读到。消费者读数据时,如果消息刚被生产出来还在页缓存里,那就直接内存到内存,完全不用碰磁盘。这也是Kafka“读写双快”的秘诀。
这里讲几个实操要点:页缓存由操作系统管理,所以Kafka的JVM堆内存不能设置得过大。我见过有人把Kafka堆设成60G,结果页缓存被压缩得几乎没有,消息一多就疯狂磁盘IO,吞吐断崖式下跌。正确做法是堆内存设置为4~6G就够,系统剩余内存尽可能多留给页缓存。
3.3 零拷贝:剪掉内核态与用户态之间的反复搬运
“零拷贝”是所有Kafka性能讲解都绕不开的点,但要讲清楚得先看清传统IO链路有多费劲。假设一个消费者要从Kafka读1MB数据,传统read+write路径是:磁盘 -> 页缓存 -> 用户态缓冲区 -> socket缓冲区 -> NIC,中间要经历至少4次上下文切换、4次数据拷贝。这4次拷贝里只有第一次是从磁盘来的必要拷贝,其余几次都是内核态和用户态之间的无意义搬运。
Kafka在服务端推送数据给消费者时用了sendfile系统调用(Java侧是FileChannel.transferTo),直接把页缓存里的数据描述符交给NIC发送,数据从磁盘读到页缓存后,不再经过用户态,直接进入网卡。整条链路变成:磁盘 -> 页缓存 -> NIC,只发生2次上下文切换、2次数据拷贝。数据量大时,省掉的那两次拷贝就是几GB的内存搬运量,收益极为可观。
还有个小知识点:零拷贝不只是sendfile,还有mmap。Kafka的索引文件用了mmap映射,也就是把索引文件映射到进程地址空间,直接按内存方式访问。但注意Kafka的数据文件本身没有用mmap,而是用sendfile推给消费者,原因在于数据文件又大又不定长,mmap容易出现缺页中断反而降低性能。
3.4 稀疏索引与二分查找:让定位消息不扫描全文件
Kafka按时间或偏移量去查找消息时,不能真的从头扫描日志文件,所以它给每个日志段配了索引文件。这里的关键词是“稀疏索引”,索引文件里并不是每条消息一个索引项,而是一段距离记一条。默认log.index.interval.bytes=4096,也就是每写约4KB数据才记录一条索引项。
稀疏索引有两个好处:索引文件体积小,方便用mmap映射到内存,读索引就跟读内存一样快;同时查找时通过二分定位到最近的索引项后,再从该位置在日志段里做小范围顺序扫描补全差值,代价可忽略。
这个设计特别能体现Kafka“为了吞吐量极尽优化”的风格:连“如何快速定位数据”都要保证不能成为性能瓶颈。
4. 批量与压缩:用小开销换大吞吐的绝招
存储层是物理极限的突破,而批量与压缩则是进一步把IO传输的“体积账”算到了极致。这一层讲的是Kafka怎么让每一次IO、每一比特网卡都尽量多干活。
4.1 批量是Kafka吞吐的灵魂
前面讲生产端时提过批量,这里需要更深入地理解:Kafka在服务端、生产端、消费端的整个数据链路都以“批”为单位。生产者往RecordAccumulator攒批,broker按“段”存储批量数据,消费者按批拉取批量处理。
这个设计的最核心好处是减少IO次数和网络包数量。IO的本质开销是建立连接、发送请求、等待响应,而不是传输的比特数。同样是100万条消息,分成10万个批(每批10条)和1万个批(每批100条),前者产生的系统调用、网络往返、服务端处理请求数是后者的十倍。这就是批量化带来数量级提升的根本。
生产端控制批量的两个关键参数batch.size和linger.ms经常被调错。batch.size默认16KB是偏保守的。注意它存的是未压缩数据的总大小,如果消息单条大,批次容量装不了几条;如果消息很小,16KB也能装上千条。实操上,针对高吞吐场景,我建议把batch.size至少调整到32KB~64KB,再用linger.ms微调延迟。linger.ms每增加5~10ms,往往能多凑出好几倍的批次大小,吞吐提升立竿见影。
4.2 压缩算法怎么选:一个表格讲明白
Kafka的压缩设计得当的话,能显著压降网络带宽和磁盘占用。需要注意的是,消息压缩发生在Producer端,解压发生在Consumer端,而broker只负责把压缩过的字节流原样存储转发,本身不参与CPU密集的解压。所以压缩的CPU成本主要由生产者和消费者承担,不会成为broker的瓶颈。
不同压缩算法各有特点:
| 压缩算法 | 压缩比 | CPU开销 | 适用场景 |
|---|---|---|---|
| gzip | 高 | 高 | 对带宽极其敏感、CPU富裕的场景 |
| snappy | 中 | 低 | 追求吞吐、CPU优先的通用场景 |
| lz4 | 中 | 很低 | 兼顾压缩比与速度,最常用的折中方案 |
| zstd | 最高 | 中高 | 大消息多、磁盘成本高,CPU可接受的场景 |
我在实际业务里通常首选lz4,偶尔切到zstd。两者的最大差别不在于单条消息的压缩比,而在于批量数据量大时整体节省的流量。如果一天要过10TB数据,从lz4换成zstd哪怕只多压缩掉10%,节省的就是1TB的磁盘和带宽。不过要注意,压缩比和CPU开销是一个跷跷板,选型永远要看你的瓶颈是带宽还是CPU。
4.3 从生产到消费,一次完整的批量旅程
把批量串起来看更有画面感:生产者把500条消息攒成一个批,用lz4压缩后从64KB变成不到20KB,发给broker;broker把批按消息格式原样追加进日志段,不做解压、不做逐条解析;消费者过来拉取时,broker直接在页缓存层面用sendfile把整块压缩数据包推给消费端;消费者拿到数据后一次性解压,再逐条处理。
这条链路里,Kafka的聪明之处在于:整个传输过程始终以“压缩包”为单位移动,谁也不逐条展开。对比一下其他消息队列,如果每条消息单独序列化、单独写入、单独推送,性能差距自然就拉开了。
5. 面试回答框架:一口气讲清楚还不丢分
掌握了原理,最后回到面试场景本身。很多人原理都知道,但一被追问就东一句西一句。我建议你按下面的框架组织语言,稳、准、有节奏。
5.1 一个“总-分-总”的回答模板
拿到问题先别急着抖术语,先给面试官一个总纲,让他知道你要从几个维度讲:
“Kafka的高吞吐不是一个单点技术造成的,而是从架构分治、顺序写盘、页缓存、零拷贝到批量压缩一整套设计共同作用的结果。我从这五个层面来讲。”
然后按顺序展开,每层两到三句话:
- 架构层:主题分成多个分区,分布在多台broker上,读写天然并行;消费者组内每个消费者各拉各的分区,互不争抢,加机器就能扩展吞吐。
- 存储层:所有数据顺序追加写日志,放弃随机读写性能瓶颈;顺序写比随机写快一到两个数量级,这是吞吐量的物理基础。
- 页缓存:读写都优先走Page Cache,热门数据基本不落盘就成了,读写路径都大量“命中内存”。
- 零拷贝:服务端把数据从页缓存直接送入网卡,省掉了多次内核态/用户态上下文切换和拷贝。
- 批量化:生产端的批次、服务端的段、消费端的批量拉取,都是为了让每一次IO处理更多的数据,配合压缩进一步降流量。
最后收个尾,显得有深度:> “这些设计本质上都是在做同一件事:把慢速的IO次数尽可能减少,把每次IO搬运的数据量尽可能加大,同时让并行度随硬件水平扩展。理解到这一层,再看各类参数调整,就都顺理成章了。”
这个回答的妙处在于,面试官听到的是“系统设计逻辑”,而不是“知识点罗列”。
5.2 想加分?带上一两个具体参数
面试官听完你的框架,很可能接着问“你实际调过哪些参数提升吞吐”。这里就是你拉差距的地方。分享几个我实际验证过、且很容易讲清楚的参数调优点:
batch.size调大到32KB以上、linger.ms设成5~10ms,单生产者吞吐能提升一到三倍,代价是多几十毫秒延迟。compression.type设成lz4,在高吞吐场景下能节省30%以上的带宽,且CPU开销几乎可以忽略。acks=all配合min.insync.replicas=2,在数据安全和高吞吐之间取得很好的平衡,不需要为了吞吐牺牲一致性。- broker端的
num.network.threads和num.io.threads建议分别设置成CPU核数的2倍和1倍左右,让网络接入和磁盘IO处理各自不再成为瓶颈。 log.segment.bytes建议默认的1GB不要随便动,频繁滚动段会产生小文件导致IO毛刺。
这些参数每一个我都亲自压测过,讲出来时顺带提到你的实测环境(比如3节点集群、万兆网卡、某个吞吐数值),说服力直接拉满。
5.3 面试官常追的四个问题,提前想好答案
这个问题大概率会有追问,提前备好这几个就稳了:
问:为什么Kafka不用零拷贝也能跑这么快?
答:零拷贝解决的是“读Path”的效率。写入吞吐主要靠顺序写和页缓存,读取时如果不走零拷贝,消费者每拉一批数据,内核和用户态就多几次搬运,当消费者数量多、拉取频繁时,CPU和内存带宽会被大量耗掉。零拷贝配合页缓存,才能支撑Kafka“一个broker支撑几百个消费者”的场景。
问:顺序写日志会不会导致文件碎片、磁盘碎片?
答:Kafka把日志切分为固定大小的Segment,写满一个滚动写下一个,按时间删除过期段。文件本身是连续的,删除后空间整块释放,不会出现传统数据库那种“页级碎片”。另外依赖文件系统的fallocate预留空间,基本不会出现过多碎片问题。
问:可以无脑调大batch.size吗?
答:不行。batch.size过大会导致消息在生产者内存里滞留过久,延迟变高,同时buffer.memory不够容易报RecordTooLargeException或者TimeoutException。它和linger.ms、max.request.size要配合着调,调到“刚好能把网卡打满”就可以了。
问:页缓存和Kafka自身的内存怎么分工?
答:Kafka的堆内存只管运行时的管理对象、请求处理、网络缓冲等少量东西,真正的大数据流量全部走操作系统的页缓存。所以Kafka JVM堆设置4~6G即可,宿主机剩余内存尽量空出来给页缓存。堆设得越大,GC越频繁,反而拖累吞吐。
6. 从原理到调优:把Kafka性能潜力真正压出来
原理懂了,面试能过了,但真正到了生产环境,你会发现“知道原理”和“性能达标”之间还差好几个调优坑。这一章把实战中真正影响吞吐的配置和排障思路完整梳理一遍。
6.1 Broker端配置:别让核心参数拖后腿
server.properties里这几项直接影响broker的吞吐红线,挨个说:
num.network.threads:负责接收网络请求的线程数。默认值是3,这个数值在流量稍大的场景根本不够用。建议至少设置成CPU核数的2倍,万兆网卡下甚至可以更高。
num.io.threads:负责处理磁盘写入和请求响应的线程。建议接近CPU核数,不要设得太高,过高反而会增加线程切换开销。
num.replica.fetchers:follower从leader拉取数据的线程数。多副本场景下这个值太小会导致副本同步速率跟不上,ISR频繁收缩。3副本集群建议至少4。
log.flush.interval.messages和log.flush.interval.ms:这两个默认值其实不需要调,更不要为了“稳妥”把它设成1。Kafka依赖页缓存后台刷盘,强制高频flush会把自己的顺序写优势全部废掉,让它变成跟普通消息队列一样的“每次写盘”。
磁盘选型:生产级别直接上SSD,这是性价比最高的提升方式。不要用RAID5,写入性能会很难看,推荐多块盘做RAID10,或者使用单块数据盘按目录规划。
6.2 生产者参数:从“默认配置”到“高吞吐配置”
请求高吞吐业务,不要用默认参数裸奔。我的常用高吞吐配置如下:
acks=all linger.ms=10 batch.size=65536 buffer.memory=67108864 compression.type=lz4 max.in.flight.requests.per.connection=5解释一下关键设定原因:linger.ms=10是给批量一个攒积时间窗口,实测10ms在很多业务场景下能把批次填满而延迟可接受;batch.size=65536允许一个批次装更多消息,减少请求数;buffer.memory=67108864(64MB)是生产者缓冲区总大小,如果看到TimeoutException异常,优先排查是不是这条消息发送速率超过缓冲区容量。
这条配置在高吞吐场景下,相比默认配置通常能提升2~4倍单生产者吞吐。但我提醒一句:调参要配合ack模式考虑,acks=all虽然多了等待ISR确认的时间,但配合多批次并行发送后,吞吐下降并不夸张。
6.3 消费者参数:别忽略了消费端吞吐
很多人以为消费端配置无所谓,其实消费端同样会压住全链路吞吐。几个关键参数:
fetch.min.bytes:一次拉取最小字节数,调大到1MB以上后消费者单次拉取的数据量大幅提升,网络往返次数大幅下降。
fetch.max.wait.ms:配合上面的参数使用。如果数据量一直凑不够fetch.min.bytes,最多等待多久返回。默认500ms,对吞吐敏感场景可以适当调高到1~2秒。
max.poll.records:单次poll返回的最大消息条数。调大可以降低处理时的上下文切换,但要注意单批处理时间别超过max.poll.interval.ms,不然消费者会被踢出消费组,导致频繁rebalance。
我见过一个很典型的消费端问题:消费者代码里每条消息处理完后单独提交offset,开着事务去操作数据库,导致消费端吞吐只有几百条每秒。后来改成批量提交offset、批量写库,直接把消费端顶到数万条每秒。原理就是“把逐条IO改成批量IO”,万变不离其宗。
6.4 高吞吐场景下最常见的4个性能故障
实战中如果你的Kafka吞吐达不到预期,优先排查下面四种情况:
| 症状 | 首要怀疑 | 定位方法 |
|---|---|---|
| 生产端延迟大、发送超时 | buffer.memory太小或batch.size太大导致积压 | 查生产者JMX指标buffer-exhausted-rate,升高即是 |
| 磁盘IO util接近饱和 | 页缓存不足或段文件频繁滚动 | 查iostat -x 1查看util,并检查物理内存是否被其他进程吃满 |
| 消费者拉取慢、消费延迟高 | 单次拉取数据量太小 | 把fetch.min.bytes调大后观察拉取请求数是否下降 |
| broker CPU飙高 | 解压/压缩开销过大或线程数不合适 | 用top -H看线程CPU占用,确认是不是压缩算法太耗CPU |
第一个场景还有个隐藏坑:当批量调得很大但buffer.memory不够时,生产者会反复超时重试,反而把吞吐拉低。记得监控record-error-rate指标,超时异常一旦上扬,先加buffer.memory再谈别的参数。
第二个场景在运维上很容易被忽视。因为Kafka进程的JVM内存看着不高,很多运维会习惯性地把机器上大部分内存分配给其他应用,导致页缓存空间不足。我给过很多团队相同的建议:如果这台机器只跑Kafka,堆内存4~6G,其余内存都留给页缓存,这是充分利用Kafka设计特性的前提。
第四个场景提个醒:有时候CPU高不是因为Kafka本身,而是因为它所在机器身兼数职,比如还跑着日志采集或搜索组件。生产环境我一般强烈建议让Kafka独占节点,不然页缓存和CPU资源互相争抢,吞吐波动会非常难排查。
写在最后
回看这个面试题,最有价值的地方其实不是记住“顺序写、零拷贝”这八个字,而是通过这道题理解一个通用工程方法:当你要做一个极高吞吐的系统时,优先想清楚怎么减少慢速IO的次数,怎么让每次IO搬运更多数据,怎么让硬件和系统机制帮自己多干活,然后在安全和性能之间做出明确的取舍。
我在日常工作中经常被问到“Kafka到底能扛多大流量”,我的回答通常是:“跟你怎么配置有关系,更跟你是否理解它为什么快有关系。”理解了它为什么快,你才不会拿着默认参数跑业务,才不会堆了30个消费者还解决不了消费积压,更不会在面试时被追问两句就额头冒汗。
这道网易二面的题,答得好的不一定是背得最熟的人,但一定是能把这个链条讲成故事的人。希望这篇文章帮你把这个故事完整地装进脑子里,下次再有人问起,你能从架构讲到存储,从参数讲到排障,把Kafka的底牌亮得明明白白。