4. Kafka生产调优

生产端的调优,说白了就是让数据又快又稳地落到Kafka里。我见过不少团队,Consumer端调得飞起,Producer端却用着默认配置跑线上——结果呢?要么延迟高得离谱,要么丢数据丢到怀疑人生。今天咱们就把Producer这块掰开揉碎,从参数到策略,一个一个说清楚。

4.1 Producer核心参数调优

先看几个最关键的参数。我个人习惯,接手一个新集群,第一件事就是检查Producer端的这几个配置。

4.1.1 acks:确认级别

这个参数决定了Producer要等多少个副本确认,才算发送成功。它有三个取值:

取值 含义 可靠性 延迟 典型场景
acks=0 Producer发完就认为成功,不等待任何确认 极低 最低 指标采集、日志(可丢)
acks=1 等待Leader副本确认写入 中等 中等 大部分业务场景(默认值)
acks=all(或-1) 等待所有ISR副本确认写入 最高 最高 金融、交易、订单等核心数据
我曾经踩过一个坑:线上订单系统用了acks=1,某天Leader副本所在Broker宕机,未同步的数据全丢了。虽然概率低,但核心业务千万别省这个配置。acks=all配合min.insync.replicas一起用,才是正经的「不丢数据」。

4.1.2 batch.size 与 linger.ms

这两个参数是「双胞胎」,一起调才有效果。

  • batch.size:Producer会把发往同一分区的消息攒成一个批次,默认16KB。批次满了就发出去。
  • linger.ms:批次没满,但等了这么久,也发出去。默认0毫秒,即立即发送。

你想想看,如果linger.ms=0,那batch.size基本就废了——消息一来就发,根本攒不成批次。我建议这样调:

我的调优习惯:对于吞吐量敏感的场景,把linger.ms设为5-20ms,batch.size设为32KB-64KB。这样既能攒批减少网络开销,又不会让延迟高到用户感知。记得配合compression.type一起用,效果翻倍。

4.1.3 compression.type:压缩算法

Producer端压缩,网络传输和磁盘存储都省空间。Kafka支持三种算法:

算法 压缩比 CPU开销 推荐场景
gzip 高 高 带宽瓶颈、文本数据
snappy 中等 低 通用场景,平衡之选
lz4 中等 极低 高吞吐、延迟敏感
zstd 最高 中等 存储成本敏感、大消息

我个人偏好lz4。为什么?压缩比和snappy差不多,但CPU开销更低。线上压测过,同样吞吐量下,lz4的CPU使用率比snappy低15%左右。当然,如果你对存储成本特别敏感,zstd是更好的选择——压缩比能到gzip的级别,但速度更快。

4.2 分区策略选择

消息到底发到哪个分区?这个选择直接影响数据倾斜和消费效率。

4.2.1 默认分区策略

  • 有key:对key做murmur2哈希,然后对分区数取模。相同key永远进同一分区。
  • 无key:轮询(Round-Robin)或粘性分区(Sticky Partitioner)。

粘性分区是2.4版本引入的改进。它会把一批消息尽量发到同一个分区,攒够batch.size再换下一个。相比轮询,粘性分区能显著减少小批次数量,提升吞吐量。

4.2.2 自定义分区策略

什么时候需要自定义?我遇到过这样一个场景:业务方要求某个维度的数据必须有序,但默认哈希会导致某些分区数据量特别大。解决办法是自定义分区器,按业务ID做二次哈希,保证均匀分布的同时维持局部有序。

// 自定义分区器示例
public class BusinessPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes,
                         Object value, byte[] valueBytes, Cluster cluster) {
        // 按业务ID哈希,保证均匀
        int partitionCount = cluster.partitionCountForTopic(topic);
        String businessId = extractBusinessId(key.toString());
        return Math.abs(businessId.hashCode()) % partitionCount;
    }
}
注意:自定义分区器一定要处理好close()方法,释放资源。我曾经见过一个线上事故,分区器里开了数据库连接没关,导致Producer频繁Full GC。

4.3 幂等性与事务

这两个机制解决的是「消息重复」和「跨分区原子性」的问题。说白了,就是让Producer变得靠谱。

4.3.1 幂等性 Producer

开启方式很简单:enable.idempotence=true。它通过Producer ID(PID)和序列号(Sequence Number)来去重。Broker会检查序列号,如果发现重复,直接丢弃。

但要注意几个限制:

  • 只能在acks=all下生效
  • 单分区内保证幂等,跨分区不保证
  • 重启后PID会变,之前的序列号失效
我曾经遇到过:某次Broker宕机恢复后,Producer重试导致大量重复消息。开启幂等性后,这个问题就解决了。但记住,幂等性不是万能的——它解决的是「重试导致的重复」,不是「业务逻辑重复」。

4.3.2 事务性 Producer

事务解决了跨分区、跨Topic的原子性问题。它引入了Transaction Coordinator和Transaction Log,通过两阶段提交实现。

使用事务的步骤:

  1. 初始化事务:producer.initTransactions()
  2. 开启事务:producer.beginTransaction()
  3. 发送消息:正常producer.send()
  4. 提交或回滚:producer.commitTransaction() 或 producer.abortTransaction()
// 事务性Producer示例
producer.initTransactions();
try {
    producer.beginTransaction();
    producer.send(new ProducerRecord<>("topic-a", "key1", "value1"));
    producer.send(new ProducerRecord<>("topic-b", "key2", "value2"));
    producer.commitTransaction();
} catch (Exception e) {
    producer.abortTransaction();
    // 记录异常,后续重试
}
我的建议:事务的开销不小,不要滥用。只有真正需要「要么全成功,要么全失败」的场景才用。比如:从Kafka消费数据,处理后写回Kafka,同时更新数据库——这种场景下事务能保证Exactly-Once语义。

4.4 核心知识体系

下面这张图总结了Producer调优的核心脉络,你可以对照着梳理自己的配置:

Kafka Producer 调优核心体系 Producer 调优 参数调优 分区策略 可靠性机制 acks batch.size linger.ms compression.type 默认策略 自定义策略 幂等性 事务 核心原则:先保证可靠性,再优化吞吐量,最后压延迟 线上环境建议开启幂等性,核心业务使用事务 + acks=all

4.5 调优实战建议

最后,给几个我实际用过的调优组合,你可以直接参考:

场景 acks batch.size linger.ms compression 幂等性 事务
日志采集(可丢) 0 64KB 10 lz4 否 否
业务消息(通用) 1 32KB 5 lz4 是 否
金融交易(核心) all 16KB 2 snappy 是 是
大数据批处理 1 128KB 50 zstd 是 否
记住:没有银弹。每个场景的流量模型、数据大小、延迟要求都不一样。我每次调优都会先做压测,用kafka-producer-perf-test.sh跑几轮,看实际效果再微调。别信网上所谓的「最佳配置」,你的业务数据说了算。