4. 批量发送与吞吐量优化:batch.size与linger.ms的调优策略
好,咱们进入第四章。这一章聊的是吞吐量和延迟的平衡问题。说白了,就是怎么让Kafka生产者发得快,但又不能把消息憋太久。
很多新手上来就调batch.size和linger.ms,结果要么吞吐上不去,要么延迟高得离谱。我刚开始搞Kafka时也踩过这个坑——调大batch.size后吞吐确实上来了,但业务方投诉说消息延迟从5ms飙到了200ms。嗯,这就是典型的“顾此失彼”。
4.1 批量发送的核心机制
先搞清楚一件事:Kafka生产者不是来一条发一条的。它会先把消息攒起来,攒够一批再一起发。这个“攒”的过程,就是批量发送。
为什么要这么做?你想想看,每条消息都单独发一次网络请求,光TCP握手和协议开销就能把吞吐拖死。批量发送能显著减少网络往返次数,提升吞吐量。
核心公式:吞吐量 ≈ 单次请求消息数 × 每秒请求数。批量越大,单次请求的消息数越多,吞吐越高。
但这里有个矛盾:攒得越多,等待时间就越长。这就是延迟和吞吐的天然冲突。
4.2 batch.size:攒多少才算够?
batch.size控制的是单个批次的最大字节数。默认值是16KB。注意,它不是指“必须攒满16KB才发”,而是“批次大小上限”。
我个人习惯把这个参数和消息的平均大小挂钩。举个例子:
- 如果每条消息1KB,16KB的batch能装16条,还行。
- 如果每条消息100字节,16KB能装160条,绰绰有余。
- 如果每条消息10KB,16KB只能装1条,那批量效果基本为零。
我在项目中遇到过这样一个场景:业务方上报的日志每条只有200字节左右,但QPS高达10万。默认16KB的batch.size完全够用,因为很快就能攒满。但另一个场景是推送图片元数据,每条消息8-12KB,16KB的batch经常只装1条就发了,吞吐上不去。后来我把batch.size调到了64KB,吞吐提升了将近3倍。
调优建议:先算一下消息平均大小,然后让batch.size能装下32-64条消息。比如消息平均1KB,batch.size设成32KB-64KB比较合理。
4.3 linger.ms:等多久才算合适?
linger.ms是“最多等多久”。默认值是0,意思是“有消息就发,不等”。
等等,这跟批量发送不是矛盾吗?
其实不矛盾。当linger.ms=0时,生产者确实会立即发送,但发送的是当前批次里的消息。如果此时批次还没满,那就发一个“半满”的批次。这样批量效果就大打折扣。
我建议这样理解:
batch.size决定“攒多少”linger.ms决定“最多等多久”- 两者是“或”的关系——谁先满足就触发发送
举个例子:batch.size=32KB,linger.ms=10ms。如果10ms内攒满了32KB,立即发;如果10ms到了还没攒满,也发,发的是当前已攒的数据。
注意:linger.ms设得太大,延迟会线性增长。我曾经见过有人设成500ms,结果吞吐是上去了,但业务方投诉说消息延迟半分钟。这显然不行。
4.4 延迟与吞吐的平衡策略
怎么平衡?没有万能公式,但有套路。我一般按以下步骤来:
- 先定延迟目标。业务允许的最大延迟是多少?比如100ms、500ms?
- 再算吞吐目标。每秒需要发送多少条消息?每条多大?
- 然后调linger.ms。一般设成延迟目标的10%-20%。比如延迟目标100ms,linger.ms设10-20ms。
- 最后调batch.size。保证在linger.ms时间内能攒够一个合理的批次。
下面这张图能帮你理清思路:
4.5 实战配置示例
光说理论不行,咱们直接上配置。假设有两个典型场景:
| 参数 | 场景A:实时风控 | 场景B:日志采集 |
|---|---|---|
| 延迟要求 | < 50ms | < 5s |
| 消息大小 | ~500字节 | ~2KB |
| QPS | 5000 | 50000 |
| batch.size | 16KB | 128KB |
| linger.ms | 5ms | 50ms |
| compression.type | none | snappy |
场景A是实时风控,延迟敏感。我设了5ms的linger.ms,16KB的batch.size。因为消息小、QPS高,5ms内基本能攒满一个批次,延迟可控。
场景B是日志采集,吞吐优先。我设了50ms的linger.ms,128KB的batch.size,还开了snappy压缩。这样单次请求能发更多消息,吞吐量拉满。
小技巧:开了压缩后,batch.size可以适当设大一些。因为压缩后的数据体积更小,同样的batch.size能装更多原始消息。
4.6 避坑指南
最后分享几个我踩过的坑:
- 坑一:把batch.size设得特别大,比如1MB。结果内存压力飙升,GC频繁,反而拖低了吞吐。建议不要超过512KB。
- 坑二:linger.ms设成0,然后抱怨批量没效果。嗯,你都没给批次攒的时间,当然没效果。
- 坑三:只调batch.size不调linger.ms。如果消息量不大,batch永远攒不满,等于没调。
- 坑四:忽略
max.request.size。这个参数默认1MB,如果batch.size超过它,会报错。记得同步调整。
我曾经在一个日志采集项目里,把batch.size从16KB调到256KB,但忘了调linger.ms(默认0)。结果吞吐一点没变,因为消息一来就发了,根本攒不成大批次。后来把linger.ms设成20ms,吞吐直接翻倍。