Kafka 可靠性语义
Kafka 的可靠性不是一个单独配置决定的,而是 Producer、Broker、副本、Consumer Offset 和业务处理共同决定的结果。
三种投递语义
| 语义 | 含义 | 典型原因 |
|---|---|---|
| At-most-once | 最多一次,可能丢,不重复 | 先提交 Offset 再处理,或 Producer 不重试 |
| At-least-once | 至少一次,不丢,可能重复 | 先处理再提交 Offset,失败后重放 |
| Exactly-once | 端到端效果一次 | 幂等写入、事务、Offset 原子提交、业务幂等共同保证 |
很多系统宣称的 Exactly-once 指的是在 Kafka 内部读-处理-写链路中实现一次效果。如果消息最终写入外部数据库,还需要数据库侧幂等或事务配合。
Producer 可靠性
Producer 写入链路:
send()
│
├── 写入 Producer 缓冲区
├── Sender 线程发送到 Leader
├── Leader 追加日志
├── ISR 副本同步
└── Broker 返回 ACK关键配置:
| 配置 | 建议 | 说明 |
|---|---|---|
acks | all | 等待 ISR 副本确认 |
enable.idempotence | true | 避免 Producer 重试导致重复写入 |
retries | 保持较大 | 网络抖动时自动重试 |
delivery.timeout.ms | 合理设置 | 限制单条消息最终投递时间 |
max.in.flight.requests.per.connection | <= 5 | 幂等生产者要求 |
acks 的影响
| acks | 成功返回条件 | 风险 |
|---|---|---|
0 | 不等待 Broker 确认 | Broker 未收到也算成功 |
1 | Leader 写入成功 | Leader 故障且 Follower 未同步时丢失 |
all | ISR 满足确认条件 | 配合 min.insync.replicas 最可靠 |
Broker 副本可靠性
推荐生产配置:
default.replication.factor=3
min.insync.replicas=2
unclean.leader.election.enable=false对应 Producer:
acks=all
enable.idempotence=true这组配置的含义:
- 每条消息有 3 个副本
- 至少 2 个 ISR 副本可用才允许成功写入
- 不允许从落后副本中选 Leader
- Producer 重试不会在同一 Partition 内写出重复消息
写入失败比丢数据更好
当 ISR 数量低于 min.insync.replicas 时,Kafka 会拒绝写入。这看起来降低了可用性,但它阻止了“只有 Leader 单副本写入成功,随后 Leader 故障导致消息丢失”的风险。
Consumer Offset 可靠性
Consumer 的 Offset 提交位置决定了失败后的行为。
先提交再处理
poll message
│
├── commit offset
└── process message如果提交后处理失败,重启后不会再消费这条消息,结果是丢消息。这属于 At-most-once。
先处理再提交
poll message
│
├── process message
└── commit offset如果处理成功但提交失败,重启后会再次消费这条消息,结果是重复处理。这属于 At-least-once。
对大多数业务,At-least-once + 业务幂等是更实用的方案。
幂等生产者
幂等生产者解决的是 Producer 重试导致的重复写入。
Producer PID = 1001
Partition orders-0
Sequence: 0, 1, 2, 3...Broker 会记录每个 Producer 在每个 Partition 上的序列号。如果网络异常导致 Producer 重发相同序列号的批次,Broker 可以识别并去重。
幂等生产者保证:
- 单 Producer Session
- 单 Partition
- 重试场景下不重复写入
它不保证:
- 跨 Topic 原子性
- 跨 Partition 原子性
- 外部数据库写入只执行一次
事务
Kafka 事务用于把多条写入和 Offset 提交绑定为一个原子操作。
典型流处理链路:
Consumer 读取 input topic
│
├── 处理消息
├── Producer 写 output topic
├── sendOffsetsToTransaction()
└── commitTransaction()事务提交后:
- 输出 Topic 的消息对
read_committedConsumer 可见 - 输入 Topic 的 Offset 随事务一起提交
- 如果事务中止,输出消息不可见,Offset 也不会提交
事务示例
props.put("transactional.id", "order-pipeline-1");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
producer.initTransactions();
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
producer.beginTransaction();
try {
for (ConsumerRecord<String, String> record : records) {
producer.send(new ProducerRecord<>("order-events", record.key(), transform(record.value())));
}
producer.sendOffsetsToTransaction(currentOffsets(records), consumer.groupMetadata());
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
}
}Consumer 需要设置:
isolation.level=read_committed顺序性保证
Kafka 只保证 Partition 内有序,不保证 Topic 全局有序。
如果需要同一订单的事件有序:
new ProducerRecord<>("orders", orderId, event);相同 Key 会进入同一 Partition,因此同一订单的事件保持顺序。
影响顺序的因素:
| 因素 | 影响 |
|---|---|
| Key 变化 | 同一业务实体可能进入不同 Partition |
| 增加 Partition | Key 到 Partition 的映射可能变化 |
| Producer 重试 | 未开启幂等时可能乱序 |
| 多个 Producer | Kafka 不保证不同 Producer 之间的全局顺序 |
端到端可靠性方案
日志采集
acks=1
enable.idempotence=true
enable.auto.commit=true可接受少量重复或丢失,优先吞吐和成本。
订单事件
replication.factor=3
min.insync.replicas=2
acks=all
enable.idempotence=true
enable.auto.commit=false消费端先处理成功,再手动提交 Offset。下游写数据库时使用业务唯一键做幂等。
流处理
enable.idempotence=true
transactional.id=固定实例 ID
isolation.level=read_committed读 Kafka、写 Kafka、提交 Offset 使用事务绑定。