4. 消息发送端优化:批量发送、异步发送、压缩机制、合理设置acks
好,咱们接着聊消息发送端的优化。说实话,很多团队在排查消息延迟问题时,第一反应总是盯着网络、盯着Broker,却忽略了发送端本身。我见过太多案例——客户端代码写得糙,再好的集群也扛不住。
今天这节,我重点讲四个方向:批量发送、异步发送、压缩机制、acks设置。这四个点,每一个我都踩过坑,也靠它们救过场。
4.1 批量发送:攒一波再发,别一条一条扔
先问个问题:你发一条消息,网络开销有多大?
一条1KB的消息,加上TCP握手、协议头、确认包,实际传输量可能是好几倍。如果你每秒发1000条,那就是1000次网络往返。说白了,大部分时间都花在“来回跑”上了。
批量发送的思路很简单:把多条消息攒成一个批次,一次性发出去。这样网络开销被摊薄,吞吐量能翻好几倍。
核心参数:
batch.size:批次大小,默认16KB。我个人习惯设到32KB~64KB,具体看消息体大小。linger.ms:等待时间,默认0。我建议设到5~20ms,给批次“攒够”的机会。
举个例子,我之前帮一个金融项目调优,他们每条消息不到1KB,但每秒要发几万条。一开始延迟高得离谱,我一看配置——linger.ms=0,消息到了就发,根本不成批。改成linger.ms=10后,吞吐量直接翻了三倍,延迟反而降了。
小技巧:如果业务对延迟极其敏感(比如交易系统),linger.ms可以设小一点,比如2ms。别设0,那等于放弃批量。
4.2 异步发送:别让发送线程卡住
同步发送,说白了就是“发一条,等一条”。你想想看,如果网络延迟是10ms,你发1000条消息,光等待就花了10秒。这谁受得了?
异步发送就不一样了。你把消息丢给一个缓冲区,发送线程继续干别的事。后台有专门的I/O线程负责真正发出去。这样,你的业务线程几乎不会被阻塞。
我记得有个电商项目,大促期间消息量暴增,他们用的就是同步发送,结果业务线程全堵在发送上,CPU利用率不到20%,但延迟飙到秒级。改成异步后,CPU跑到了70%,延迟降到毫秒级。
关键点:
- 使用
producer.send()的异步回调,别用.get()阻塞等待。 - 设置合理的
max.block.ms,防止缓冲区满时无限阻塞。 - 监控回调中的异常,别丢了失败的消息。
// 错误示范:同步发送,每条都等
for (Message msg : messages) {
producer.send(msg).get(); // 卡死!
}
// 正确做法:异步发送,带回调
for (Message msg : messages) {
producer.send(msg, (metadata, exception) -> {
if (exception != null) {
// 记录失败,重试或落盘
log.error("发送失败", exception);
}
});
}
注意:异步发送虽然快,但缓冲区会积压。如果生产速度远大于消费速度,内存可能爆掉。我建议配合buffer.memory参数限制缓冲区大小,并做好背压机制。
4.3 压缩机制:数据小了,传输就快了
这个道理很直白——同样的带宽,数据越小,能发的条数就越多。压缩就是帮你把消息“挤一挤”。
Kafka支持三种压缩算法:gzip、snappy、lz4。我个人的经验是:
| 算法 | 压缩比 | CPU开销 | 推荐场景 |
|---|---|---|---|
| gzip | 高(3~5倍) | 高 | 带宽瓶颈,CPU有余量 |
| snappy | 中(2~3倍) | 低 | 通用场景,平衡之选 |
| lz4 | 中(2~3倍) | 极低 | 延迟敏感,追求速度 |
我曾经在一个日志采集系统里,消息体是JSON文本,压缩前每条2KB,用snappy压缩后不到800字节。同样带宽下,吞吐量提升了2.5倍。而且snappy的CPU开销很低,几乎不影响发送延迟。
配置方式:在Producer端设置compression.type=snappy即可。注意,压缩是在批量发送时对整个批次做的,不是逐条压缩,所以效果更好。
4.4 合理设置acks:在可靠性和性能之间找平衡
acks这个参数,说白了就是“等几个副本确认了才算发送成功”。
- acks=0:发出去就不管了,最快,但可能丢消息。
- acks=1:等Leader确认,默认值,性能和可靠性都还行。
- acks=all:等所有副本确认,最安全,但延迟最高。
我见过不少团队,一上来就设acks=all,觉得这样最保险。结果延迟高得吓人,一问才知道副本数设了5个,每次都要等5个节点都写完了才返回。嗯,这其实没必要。
我的建议:
- 日志、监控等可容忍少量丢失的场景:
acks=0或acks=1。 - 订单、支付等核心业务:
acks=all,但副本数别超过3个。 - 配合
min.insync.replicas使用,比如设成2,这样即使一个副本挂了,也能保证数据不丢。
我曾经在一个支付系统中,把acks从all改成1,延迟从50ms降到了5ms。后来加了幂等性和重试机制,数据一致性也没出过问题。说白了,acks不是唯一的保障手段,别死磕这一个参数。
知识体系总览
下面这张图,我把这四个优化点的关系和核心逻辑画出来了。你可以对照着看,哪个环节出了问题,就去调哪个参数。
好了,这节的内容就这些。批量发送、异步发送、压缩、acks——这四个点你调好了,发送端的延迟基本就稳了。下一节咱们聊聊Broker端怎么优化,到时候再细说。