一条消息的发送路径
生产者发送一条消息依次经历:序列化 key 与 value、分区器决定写入哪个分区、进入累加器缓冲成批、由后台发送线程发往该分区的 Leader、按 acks 配置等待确认,失败则按重试策略重发。理解这条路径,才能判断延迟与丢数分别出现在哪一环。
批量与延迟的取舍
累加器把短时间内的多条消息凑成一个批次发送,批次越大,网络与磁盘的利用率越高、吞吐越好,但单条消息的等待时间也越长。这是生产者最重要的调优取舍:需要吞吐就调大批次并允许短暂等待,需要低延迟就让消息尽快发出。
基本信息
| 关键组件 | 序列化器、分区器、记录累加器(缓冲批次)、发送线程 |
|---|---|
| 连接方式 | 只需指定两到三个 Broker 地址获取元数据,无需列全部节点 |
| 幂等 | 开启后同一生产者会话内重试不会产生重复 |
| 顺序 | 分区内有序;开启重试时需注意并发请求数对顺序的影响 |
| 主要调优点 | 批次大小、等待时间、确认级别与重试上限 |
关键参数读法
| 参数 | 作用 | 影响 |
|---|---|---|
| bootstrap.servers | 初始连接的 Broker 地址 | 写两三个即可,写错会导致元数据获取失败 |
| acks | 写入确认级别 | 越高越可靠,延迟与失败率越高 |
| enable.idempotence | 幂等写入,重试不重复 | 与 acks=all 搭配使用,生产推荐开启 |
| linger.ms | 发送前额外等待时间,用于凑批 | 调大提升吞吐,增加单条延迟 |
| batch.size | 单批最大字节数 | 过小会导致批次装不满,吞吐上不去 |
| delivery.timeout.ms | 从发送到最终失败的总时限 | 包含重试时间,设置过大会让发送长时间阻塞 |
典型使用场景
- 高吞吐日志采集:调大批次与等待时间,换取更高吞吐
- 低延迟告警事件:让消息尽快发出,容忍批次变小
- 账务类事件:acks=all 加幂等,宁可写入失败也不丢不重
- CDC 输出:变更事件需要严格保序,按主键分区并谨慎设置并发
- 压测评估:使用与生产一致的序列化方式与确认级别,否则结论不可用
生产者核心配置
生产者核心配置
bootstrap.servers=kafka1:9092,kafka2:9092
key.serializer=org.apache.kafka.common.serialization.StringSerializer
value.serializer=org.apache.kafka.common.serialization.StringSerializer
acks=all # 等 ISR 全部确认
enable.idempotence=true # 重试不产生重复
retries=2147483647 # 与幂等配合时的常见设置
batch.size=32768 # 单批 32KB,写大一些减少请求数
max.in.flight.requests.per.connection=5 # 单连接未确认请求数上限发送与回调(Java)
发送与回调(Java)
ProducerRecord<String, String> record =
new ProducerRecord<>("orders", order.getOrderId(), json);
// 指定 key 后,同一订单的变更固定进入同一分区,保证局部有序
producer.send(record, (metadata, exception) -> {
if (exception != null) {
log.error("send failed, topic={}, key={}", "orders", order.getOrderId(), exception);
} else {
log.debug("sent to partition {} offset {}", metadata.partition(), metadata.offset());
}
});
producer.flush(); // 关闭前刷新缓冲区,避免缓冲中的消息被丢弃序列化器与实际消息类型必须匹配:声明为字符串却在发送 JSON 对象,会在发送阶段抛序列化异常,报错信息往往指向客户端日志而不是业务代码,排查时先看生产者日志。
网络持续异常时,无限重试配合过大的总超时会让发送线程长时间阻塞,业务线程跟着被拖住;应把总时限控制在一个业务可接受的范围,并把失败交给应用层处理。