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 batch.size / linger.ms gzip / snappy / lz4 减少网络往返次数 0 / 1 / all 降低传输数据量 目标:高吞吐 + 低延迟 + 数据不丢

好了,这节的内容就这些。批量发送、异步发送、压缩、acks——这四个点你调好了,发送端的延迟基本就稳了。下一节咱们聊聊Broker端怎么优化,到时候再细说。

可可精品资料库