4. 生产者与消费者客户端开发

Kafka 客户端开发的核心,不是“能发能收”,而是“在异常、重试、并发和重启条件下仍然不丢不重”。本章围绕 Producer 与 Consumer 的关键配置、序列化、批量与压缩、重试与幂等、以及偏移量提交策略展开,目标是写出可上线、可观测、可恢复的生产消费程序。

一、Producer 关键配置与发送链路

一条消息从 send() 到落盘,会经历拦截器、序列化器、分区器、RecordAccumulator 缓冲、Sender 线程批量发送、Broker 写入这几个阶段。理解这条链路,才能理解各参数的作用位置。

最关键的几个参数:

  • bootstrap.servers:仅用于获取集群元数据,不是全部 Broker 列表。
  • key.serializer / value.serializer:序列化器全限定类名,必须与发送类型匹配。
  • acks:0 不等确认(可能丢)、1 Leader 落盘即返回、all(或 -1)ISR 全部确认,配合幂等必须为 all。
  • retries 与 retry.backoff.ms:可重试异常的重试次数与退避。
  • linger.ms 与 batch.size:批量发送的两个开关,前者按时间攒批,后者按字节攒批。
  • compression.type:none、gzip、snappy、lz4、zstd。
  • enable.idempotence=true:开启幂等,配合 acks=all、retries>0、max.in.flight.requests.per.connection<=5。

下面这张图展示了 Producer 的发送链路与各参数的作用点,以及幂等性依赖的配置约束。

Producer 发送链路与关键参数作用点 应用代码 producer.send() 拦截器 interceptors 序列化器 key/value.serializer 分区器 partitioner RecordAccumulator 缓冲 batch.size / linger.ms / buffer.memory Sender 线程 max.in.flight / retries / compression.type Broker Leader acks=all → ISR 全部写入 幂等:PID + SequenceNumber 幂等性(enable.idempotence=true)的强制约束 • acks = all(-1) • retries > 0(默认 Integer.MAX_VALUE) • max.in.flight.requests.per.connection ≤ 5 • 若显式设置冲突值,客户端启动即抛 ConfigException 效果:单分区内不重不丢,Broker 按 PID+Seq 去重 可重试异常回退

二、序列化机制

Kafka 只认字节数组,序列化器负责对象到字节的转换。内置的有 StringSerializer、ByteArraySerializer、IntegerSerializer 等。生产环境更常用 Avro、Protobuf、JSON Schema,配合 Schema Registry 做模式演进与兼容性校验。

自定义序列化器需实现 Serializer<T>,重写 serialize(topic, data) 与 configure()。注意:序列化器必须线程安全,因为 Producer 是线程安全的,多个线程可能共享同一个序列化器实例。反序列化器同理,且必须与序列化器严格配对,否则会出现 SerializationException 或乱码。

三、批量发送与压缩

批量发送的两个参数:batch.size 默认 16KB,linger.ms 默认 0。当 linger.ms=0 时,Sender 线程只要缓冲区有数据就立即发送,吞吐低但延迟最低。把 linger.ms 调到 5~50ms,可以让更多消息攒进同一批次,显著提升吞吐,代价是增加延迟。

压缩在批次级别进行,压缩的是整个 RecordBatch,因此批量越大,压缩收益越高。选择建议:

  • lz4:吞吐与压缩比均衡,延迟低,通用首选。
  • zstd:压缩比最高,CPU 消耗略高,适合带宽紧张场景。
  • gzip:压缩比高但 CPU 重、延迟高,逐渐被 zstd 替代。
  • snappy:老牌选择,压缩比一般。

压缩由 Producer 完成,Broker 原样存储,Consumer 解压。因此压缩类型写在消息批次头里,消费端无需额外配置即可解压。

四、重试与幂等性

可重试异常包括 NotLeaderForPartitionException、LeaderNotAvailableException、NetworkException、TimeoutException 等;不可重试异常如 RecordTooLargeException、SerializationException,重试无意义。

仅靠重试会带来重复:Broker 已写入但 ACK 丢失,Producer 重试就会写第二条。幂等性通过 Producer ID(PID)+ 分区级 Sequence Number 解决:Broker 对同一 PID 的同一分区维护序列号,重复批次直接丢弃。注意幂等只保证单分区、单会话内不重,跨会话(Producer 重启后 PID 变化)不保证,跨分区也不保证。

若需要跨会话、跨分区的精确一次,需要事务:设置 transactional.id,调用 initTransactions()、beginTransaction()、commitTransaction(),并配合 Consumer 的 isolation.level=read_committed。

五、Consumer 配置与偏移量提交

Consumer 的核心参数:

  • group.id:消费者组标识,同组内分区互斥分配。
  • enable.auto.commit:默认 true,自动提交,容易丢或重。
  • auto.commit.interval.ms:自动提交间隔,默认 5000ms。
  • auto.offset.reset:无提交偏移时的起点,earliest / latest / none。
  • max.poll.records:单次 poll 返回的最大记录数,默认 500。
  • max.poll.interval.ms:两次 poll 的最大间隔,默认 5 分钟,超时触发 rebalance。
  • isolation.level:read_uncommitted(默认)或 read_committed。

偏移量提交有两种方式:自动提交和手动提交。手动提交又分同步 commitSync() 和异步 commitAsync()。推荐模式是“处理完再提交”,即先处理消息,再提交偏移,保证至少一次语义。

Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092,kafka2:9092");
props.put("group.id", "order-consumer-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("enable.auto.commit", "false");
props.put("auto.offset.reset", "earliest");
props.put("max.poll.records", "200");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("orders"));

while (running) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));
    for (ConsumerRecord<String, String> r : records) {
        process(r);   // 业务处理
    }
    if (!records.isEmpty()) {
        consumer.commitSync();  // 处理完再提交
    }
}

如果处理逻辑可能失败,应把偏移量与业务结果放在同一事务里(如数据库事务),或使用“处理成功才提交”的模式。异步提交 commitAsync() 吞吐更高,但失败不会自动重试,通常在 onComplete 回调里记录失败,并在关闭前用 commitSync() 兜底。

常见坑:自动提交 + 多线程处理时,若某条消息处理失败但偏移已被自动提交,重启后该消息永远不会再被消费,造成数据丢失。生产环境建议关闭自动提交,改为手动提交。

六、可靠生产消费的检查清单

环节关键配置目标
Producer 可靠性acks=all, retries>0, enable.idempotence=true不丢不重(单分区单会话)
Producer 吞吐linger.ms=10~50, batch.size=64KB~256KB, compression.type=lz4/zstd提升吞吐、降低带宽
Consumer 语义enable.auto.commit=false, 处理完 commitSync()至少一次
Consumer 稳定性max.poll.records 调小, max.poll.interval.ms 合理避免 rebalance 风暴
精确一次transactional.id + isolation.level=read_committed跨分区跨会话 EOS

最后强调:所有可靠性配置都要配合监控。关注 Producer 的 record-error-rate、record-retry-rate、batch-size-avg,Consumer 的 records-lag-max、commit-latency-avg、rebalance-rate。只有指标可见,配置才真正可控。