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会变,之前的序列号失效
4.3.2 事务性 Producer
事务解决了跨分区、跨Topic的原子性问题。它引入了Transaction Coordinator和Transaction Log,通过两阶段提交实现。
使用事务的步骤:
- 初始化事务:
producer.initTransactions() - 开启事务:
producer.beginTransaction() - 发送消息:正常
producer.send() - 提交或回滚:
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();
// 记录异常,后续重试
}
4.4 核心知识体系
下面这张图总结了Producer调优的核心脉络,你可以对照着梳理自己的配置:
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跑几轮,看实际效果再微调。别信网上所谓的「最佳配置」,你的业务数据说了算。