Kafka 生产者是整个数据流的入口,其发送语义直接影响到数据的一致性和系统的可靠性。Kafka 提供了从"最多一次"到"恰好一次"的完整消息保证等级,理解这些机制需要深入到 ACK 策略、幂等性实现和事务模型的底层原理。
1. 生产者核心配置
Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092,kafka2:9092,kafka3: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"); // 等待 ISR 全部确认
props.put("retries", Integer.MAX_VALUE); // 无限重试(推荐)
props.put("enable.idempotence", true); // 开启幂等性(Kafka 0.11+)
props.put("max.in.flight.requests.per.connection", 5); // 并发请求数
// ── 性能配置 ──
props.put("linger.ms", 10); // 批量等待时间
props.put("batch.size", 32768); // 批量大小 32KB
props.put("compression.type", "zstd"); // 压缩算法
props.put("buffer.memory", 67108864); // 缓冲区 64MB
Producer<String, String> producer = new KafkaProducer<>(props);
2. ACK 策略:数据可靠性的基石
acks 参数控制生产者在收到 Broker 确认前需要等待的程度,是可靠性配置中最核心的参数:
| acks 值 | 行为 | 吞吐 | 可靠性 | 适用场景 |
|---|---|---|---|---|
0 | 发送后立即返回,不等待任何确认 | 最高 | 可能丢失 | 日志采集、允许丢失的指标 |
1 | 等待 Leader 确认 | 中高 | Leader 宕机时可能丢失 | 一般业务场景 |
all | 等待 ISR 中所有副本确认 | 较低 | 不丢失 | 金融交易、订单等关键业务 |
当 acks=all 时的完整流程:
Producer ──→ Leader(Broker 1) ──→ Follower(Broker 2)
│ │
│←── Ack (Replica OK) ─│
│ │
│←── Ack (Replica OK) ───│
│ │
Producer ←──── Ack (ISR 全部确认) ────────
配合 min.insync.replicas=2 使用:只有当 ISR 中至少有 2 个副本确认时,才认为写入成功。如果 ISR 副本数不足,生产者会收到 NotEnoughReplicasException,此时可选择阻塞等待或报错。
3. 幂等性:Exactly-Once 的基础
Kafka 0.11 引入了生产者幂等性,解决了重试导致的消息重复问题:
props.put("enable.idempotence", true); // 开启幂等性
props.put("max.in.flight.requests.per.connection", 5); // 允许并发(Kafka 1.1+)
幂等性的实现原理:
| 字段 | 说明 |
|---|---|
| PID(Producer ID) | 每个幂等性生产者被分配的唯一标识 |
| Sequence Number | 每条消息在每个 Partition 内的单调递增序号 |
| Broker 端去重 | Broker 根据 (PID, Partition, SeqNum) 三元组去重 |
Producer 发送消息:
PID=1001, Partition=0, Seq=0 → Broker 接收并存储
PID=1001, Partition=0, Seq=1 → Broker 接收并存储
PID=1001, Partition=0, Seq=1 → Broker 检测到重复,返回上次的响应(幂等去重)
PID=1001, Partition=0, Seq=3 → Broker 检测到 Seq 不连续,返回 OutOfOrderSequenceException
重要限制:
- 幂等性只在单个生产者会话和单个分区内生效
- 生产者重启后 PID 会变化
- 跨区域场景需要配合事务使用
4. 事务生产者:跨分区 Exactly-Once
事务生产者将多条消息的写入和 Offset 提交原子化,实现跨分区的"恰好一次"语义:
Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("transactional.id", "order-service-producer-1"); // 事务 ID(必须唯一)
props.put("enable.idempotence", true);
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(new ProducerRecord<>("orders", "order-001", jsonOrder));
producer.send(new ProducerRecord<>("payments", "order-001", jsonPayment));
producer.commitTransaction(); // 原子提交
} catch (Exception e) {
producer.abortTransaction(); // 回滚
throw e;
}
事务的隔离级别:
| 隔离级别 | 行为 |
|---|---|
read_uncommitted(默认) | 消费者可读取到未提交的消息 |
read_committed | 消费者只能读取到已提交的消息(事务完成后的) |
// 消费者侧配置
props.put("isolation.level", "read_committed");
5. 发送模式详解
异步发送(生产环境推荐)
// 带回调的异步发送
producer.send(new ProducerRecord<>("orders", orderId, json),
(metadata, exception) -> {
if (exception != null) {
// 异常处理:记录日志、告警、死信队列
log.error("Send failed for partition {} offset {}",
metadata.partition(), metadata.offset(), exception);
}
});
同步发送
// 阻塞直到收到 Broker 确认
RecordMetadata metadata = producer.send(record).get();
System.out.println("Sent to partition " + metadata.partition() +
" at offset " + metadata.offset());
注意:同步发送会阻塞线程,在高吞吐场景下建议使用带回调的异步发送 + semaphore 限流:
Semaphore semaphore = new Semaphore(1000); // 限制并发未确认消息数
public void sendWithBackpressure(String topic, String message) {
semaphore.acquire();
producer.send(new ProducerRecord<>(topic, message),
(metadata, exception) -> {
semaphore.release();
if (exception != null) log.error("Send failed", exception);
});
}
6. 分区器(Partitioner)
分区器决定消息发送到哪个 Partition,默认行为:
// 有 key:murmur2(key) % numPartitions
// 无 key:round-robin(sticky 策略,Kafka 2.4+)
自定义分区器
public class UserPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes,
Object value, byte[] valueBytes, Cluster cluster) {
int numPartitions = cluster.partitionCountForTopic(topic);
int vipPartition = numPartitions - 1; // 最后一个分区给 VIP 用户
String userId = (String) key;
if (userId.startsWith("VIP_")) {
return vipPartition;
}
return Math.abs(userId.hashCode()) % (numPartitions - 1);
}
@Override public void configure(Map<String, ?> configs) {}
@Override public void close() {}
}
生产者元数据与分区策略原理
Producer 维护一份 Cluster 元数据(Topic、Partition、Leader Broker 等),默认每 5 分钟(metadata.max.age.ms)刷新一次:
发送消息流程:
1. 生产者拦截器(Interceptor)处理
2. Serializer 序列化 key/value
3. Partitioner 选择 partition(优先按 key,无 key 时用 Sticky Partitioner)
4. RecordAccumulator 将消息累加到 batch(按 topic-partition 分组)
5. Sender 线程批量发送 batch 到对应 Broker
6. 等待 Broker ACK(按 acks 配置)
7. Callback 执行 / Future 返回
Sticky Partitioner(Kafka 2.4+ 默认):
- 无 key 消息不再简单 round-robin,而是"粘性"地填充同一个 partition 的 batch
- 好处:减少小 batch 数量,提升吞吐;
linger.ms内到达的消息尽量进入同一 batch - 切换 partition 时机:batch 满、linger 超时、或单 partition 未确认请求过多
消息顺序性保证
Kafka 仅在单个 partition 内保证消息顺序。如需全局有序:
// 方案 1:单 partition(吞吐受限)
props.put("max.in.flight.requests.per.connection", 1); // 严格顺序
// 方案 2:按业务键分区(同一用户订单始终进入同一 partition)
producer.send(new ProducerRecord<>("orders", userId, orderJson));
| 配置 | 顺序保证 | 吞吐影响 |
|---|---|---|
max.in.flight=1 + acks=all | 严格有序 | 最低 |
max.in.flight=5 + 幂等性 | 单分区有序(可能重排) | 高 |
max.in.flight=5(无幂等) | 无顺序保证 | 最高 |
Schema Registry 集成
生产环境强烈建议使用 Schema Registry(Confluent/Apicurio)管理消息格式:
// Avro 序列化配置
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "io.confluent.kafka.serializers.KafkaAvroSerializer");
props.put("schema.registry.url", "http://schema-registry:8081");
// 自动注册 schema(生产环境建议关闭,改为人工审批)
props.put("auto.register.schemas", false);
props.put("use.latest.version", true);
Schema Registry 的好处:
- 模式演进兼容检查(BACKWARD / FORWARD / FULL)
- 减少消息体积(仅用 schema ID,而非完整 JSON Schema)
- 多语言/多团队消费时格式一致性保障
生产者监控指标
| JMX Metric | 说明 | 告警阈值建议 |
|---|---|---|
record-error-rate | 发送错误率 | > 0.1% |
record-retry-rate | 重试率 | > 5% |
request-latency-avg | 平均请求延迟 | > 100ms |
buffer-available-bytes | 缓冲区剩余空间 | < 10% |
connection-count | 活跃连接数 | 接近 max.connections |
iotime-total | 网络 IO 耗时 | 趋势监控 |
7. 生产环境最佳实践
# ── 高可靠配置(金融/订单场景) ──
acks=all
enable.idempotence=true
retries=2147483647
delivery.timeout.ms=120000
request.timeout.ms=30000
min.insync.replicas=2
# ── 高性能配置(日志/指标场景) ──
acks=1
compression.type=lz4
linger.ms=50
batch.size=262144 # 256KB
buffer.memory=134217728 # 128MB
延伸阅读
- 消费者组与偏移量管理 — Consumer Group Rebalance、提交策略
- 集群与高可用架构 — Controller 选举、ISR 管理、故障恢复
- Kafka Streams 流处理 — KStream、窗口聚合、Exactly-Once 处理
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。