4. Kafka 生产者同步与异步发送模式对比

Kafka 生产者同步与异步发送模式对比 学习卡片

同步与异步发送的核心差异

Kafka 生产者的 send() 方法返回一个 Future<RecordMetadata>。同步发送的本质是立即调用 future.get() 阻塞等待 Broker 确认;异步发送则注册 Callback,由 Sender 线程在响应返回后回调处理。两者共用同一套缓冲与批处理机制,差异集中在调用线程是否阻塞以及异常暴露的位置。

同步发送 vs 异步发送:调用线程与 Sender 线程的交互 同步发送 send().get() 调用线程 RecordAccumulator Broker 调用线程阻塞等待 future.get() 异常在调用线程抛出 → 可立即 try/catch 异步发送 send(callback) 调用线程 RecordAccumulator Broker 调用线程立即返回,不阻塞 异常在 Callback.onCompletion() 中处理 关键维度对比 维度 同步发送 异步发送 吞吐量 低(逐条等待) 高(批量流水线) 单条延迟 可预测,含 RTT 低(不等待确认) 异常处理 try/catch 直接捕获 Callback 中判断 exception

同步发送实现与验证

同步发送适合需要严格确认每条消息写入结果的场景,例如金融交易流水、配置变更通知。以下代码向 order-events 主题发送 100 条消息,每条都等待 Broker 确认。

import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class SyncProducer {
    public static void main(String[] args) {
        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");
        props.put("acks", "all");
        props.put("retries", 3);
        props.put("linger.ms", "0");

        try (Producer<String, String> producer = new KafkaProducer<>(props)) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                ProducerRecord<String, String> record =
                    new ProducerRecord<>("order-events", "key-" + i, "order-" + i);
                try {
                    RecordMetadata meta = producer.send(record).get();
                    System.out.printf("offset=%d partition=%d%n",
                        meta.offset(), meta.partition());
                } catch (Exception e) {
                    System.err.println("发送失败: " + e.getMessage());
                }
            }
            long elapsed = System.currentTimeMillis() - start;
            System.out.printf("同步发送 100 条耗时: %d ms, 吞吐: %.1f msg/s%n",
                elapsed, 100000.0 / elapsed);
        }
    }
}
验证要点:运行后观察每条消息的 offset 是否连续递增。若某条抛出 TimeoutException,说明 Broker 未在 request.timeout.ms(默认 30000ms)内确认,需检查 Broker 负载或网络。

异步发送实现与验证

异步发送适合高吞吐、可容忍少量重试或丢失的场景,例如日志采集、埋点上报。以下代码发送同样 100 条消息,但通过 Callback 处理结果。

import org.apache.kafka.clients.producer.*;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

public class AsyncProducer {
    public static void main(String[] args) throws InterruptedException {
        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");
        props.put("acks", "1");
        props.put("retries", 3);
        props.put("linger.ms", "20");
        props.put("batch.size", "16384");

        AtomicInteger success = new AtomicInteger();
        AtomicInteger failure = new AtomicInteger();

        try (Producer<String, String> producer = new KafkaProducer<>(props)) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                ProducerRecord<String, String> record =
                    new ProducerRecord<>("order-events", "key-" + i, "order-" + i);
                producer.send(record, (metadata, exception) -> {
                    if (exception == null) {
                        success.incrementAndGet();
                    } else {
                        failure.incrementAndGet();
                        System.err.println("异步发送失败: " + exception.getMessage());
                    }
                });
            }
            producer.flush();
            long elapsed = System.currentTimeMillis() - start;
            System.out.printf("异步发送 100 条耗时: %d ms, 成功: %d, 失败: %d%n",
                elapsed, success.get(), failure.get());
        }
    }
}
关键区别:异步发送中 producer.flush() 必须调用,否则主线程可能在消息真正发出前退出。Callback 在 Sender 线程中执行,不要在其中做耗时操作,否则会阻塞后续消息的发送。

对比实验与选型依据

在同一台机器、同一主题(3 分区、acks=all)上分别运行上述两个程序,记录耗时。以下为教学示例的预期量级(实际值受硬件和网络影响):

指标同步发送异步发送
100 条总耗时约 800–1500 ms约 50–150 ms
吞吐量约 70–125 msg/s约 670–2000 msg/s
单条延迟约 8–15 ms约 0.5–1.5 ms(提交延迟)
异常暴露位置调用线程 get()Callback onCompletion()
消息顺序保证严格按发送顺序同一分区内仍有序,但跨分区不保证
选型决策:若业务要求“每条消息必须确认落盘后才继续”,选同步;若业务要求“高吞吐、可接受异步回调补偿”,选异步。折中方案:使用异步发送 + flush() 批量提交,在吞吐和可靠性之间取得平衡。