生产者基础:Producer API使用、消息发送方式(同步/异步)、acks与retries配置
好,咱们进入生产者的世界。Kafka 生产者,说白了就是那个往管道里塞数据的角色。我见过不少新手,上来就写个 Producer.send(),然后数据丢了都不知道怎么回事。今天咱们就把这块掰开揉碎,讲清楚。
Producer API 核心组件
先看一段最基础的生产者代码。我习惯从最简单的开始,再慢慢加配置。
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer =
new KafkaProducer<>(props);
producer.send(new ProducerRecord<>("my-topic", "key1", "value1"));
producer.close();
这里有几个关键点:
- bootstrap.servers:别只配一个 broker。我踩过坑,单点挂了整个集群连不上。至少配两三个。
- 序列化器:Kafka 只传字节。你传对象?得自己转。StringSerializer 是最常用的,但生产环境我更喜欢用 Avro 或 Protobuf。
- ProducerRecord:消息本体。包含 topic、partition(可选)、key、value。key 为空时,消息会轮询发到各分区。
消息发送方式:同步 vs 异步
很多人以为 producer.send() 是同步的。其实不是。它默认是异步的,返回一个 Future 对象。咱们来看看区别。
异步发送(默认)
producer.send(new ProducerRecord<>("topic", "key", "value"),
(metadata, exception) -> {
if (exception != null) {
System.err.println("发送失败: " + exception.getMessage());
} else {
System.out.println("发送成功, offset: " + metadata.offset());
}
});
异步发送不阻塞主线程。回调函数在发送完成后执行。我一般在日志采集场景用这个,吞吐量高。
同步发送
try {
RecordMetadata metadata = producer.send(
new ProducerRecord<>("topic", "key", "value")).get();
System.out.println("发送成功, offset: " + metadata.offset());
} catch (Exception e) {
System.err.println("发送失败: " + e.getMessage());
}
调用了 .get(),就变成同步了。线程会卡在这里,直到 broker 返回结果。你想想看,如果网络延迟高,这得卡多久?
acks 配置:到底等几个确认?
acks 是生产者的核心配置。它决定了 producer 要等多少个 broker 确认,才算发送成功。直接看对比:
| acks 值 | 含义 | 可靠性 | 延迟 | 适用场景 |
|---|---|---|---|---|
| 0 | 不等待确认,发完就算成功 | 最低 | 最低 | 日志、监控指标,丢几条无所谓 |
| 1 | 等待 leader 确认写入 | 中等 | 中等 | 大部分业务场景,默认值 |
| all(或 -1) | 等待 leader + 所有 ISR 副本确认 | 最高 | 最高 | 金融、订单、支付等关键数据 |
我个人习惯:
- 日志类数据用 acks=0,性能优先。
- 普通业务用 acks=1,够用了。
- 核心交易用 acks=all,配合 min.insync.replicas=2,保证至少两个副本有数据。
注意:acks=all 不代表数据 100% 不丢。如果所有 ISR 副本都挂了,数据还是会丢。所以还要配合 replication.factor 和 min.insync.replicas 一起用。
retries 配置:失败后怎么办?
网络抖动、leader 选举,这些都会导致发送失败。retries 就是重试次数。默认是 Integer.MAX_VALUE,但 2.0 之后改成了 0。嗯,这里要注意。
props.put("retries", 3);
props.put("retry.backoff.ms", 100);
重试不是越多越好。我见过一个系统 retries 设了 10 次,backoff 设了 1 秒,结果 broker 挂了 5 分钟,producer 一直在重试,消息堆积了几百万条。最后 OOM 了。
核心流程:一张图说清楚
下面这张图,把 producer 发送消息的完整流程画出来了。从序列化到分区,再到缓冲区,最后发到 broker。我每次给团队培训都拿这张图讲。
流程其实不复杂:
- 先创建 ProducerRecord,指定 topic 和消息内容。
- 序列化器把 key 和 value 转成字节。
- 分区器决定消息去哪个分区。有 key 就 hash,没 key 就轮询。
- 消息进缓冲区(RecordAccumulator),攒一批再发。
- Sender 线程从缓冲区取数据,批量发送到 broker。
- broker 根据 acks 配置返回确认。
- 回调函数处理结果。
实战配置清单
最后,给出一份我常用的生产配置。你可以直接拿去用,再根据实际情况微调。
Properties props = new Properties();
props.put("bootstrap.servers", "broker1:9092,broker2:9092,broker3:9092");
props.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
// 可靠性配置
props.put("acks", "all");
props.put("retries", 3);
props.put("retry.backoff.ms", 200);
props.put("enable.idempotence", true); // 幂等性,防止重复
// 性能配置
props.put("batch.size", 16384); // 16KB
props.put("linger.ms", 5); // 最多等5ms
props.put("buffer.memory", 67108864); // 64MB
props.put("compression.type", "snappy"); // 压缩
// 超时配置
props.put("request.timeout.ms", 30000);
props.put("delivery.timeout.ms", 120000);
好了,生产者基础就讲到这里。配置项很多,但核心就三个:acks 管可靠性,retries 管容错,batch.size 和 linger.ms 管性能。你想想看,是不是这个道理?