kafka API操作之生产者
Kafka 生产者 API 详解与实践
Kafka 生产者(Producer)是消息的发送端,负责将业务数据发送到 Kafka 集群。通过 Kafka 提供的 Producer API,我们可以灵活配置生产者行为,支持同步 / 异步发送、自定义分区策略、消息压缩等功能。本文将详细介绍生产者的实现步骤、核心配置及不同发送方式的代码示例
生产者核心概念与配置
核心配置参数
创建生产者时,需通过 Properties 对象配置关键参数,其中必选参数有三个:
| 参数名 | 作用 | 示例值 |
|---|---|---|
bootstrap.servers |
指定 Kafka 集群地址(多个用逗号分隔) | localhost:9092 |
key.serializer |
消息键(Key)的序列化类(需实现 Serializer 接口) |
org.apache.kafka.common.serialization.StringSerializer |
value.serializer |
消息值(Value)的序列化类 | org.apache.kafka.common.serialization.StringSerializer |
常用可选参数(参考 ProducerConfig 类):
| 参数名 | 作用 | 默认值 |
|---|---|---|
acks |
消息确认级别(0:不确认;1:Leader 确认;-1/all:Leader + 所有 ISR 副本确认) | 1 |
retries |
发送失败后的重试次数 | 0 |
batch.size |
批次大小(达到该值后批量发送,单位:字节) | 16384(16KB) |
linger.ms |
批处理等待时间(若未达 batch.size,超时后也会发送) |
0(立即发送) |
buffer.memory |
发送缓冲区大小(消息暂存此处等待发送) | 33554432(32MB) |
compression.type |
消息压缩算法(none/gzip/snappy/lz4) | none |
生产者工作原理
Kafka 生产者发送消息的流程如下:
- 消息被封装为
ProducerRecord(包含主题、键、值、分区等信息)。 - 消息经序列化后进入发送缓冲区(
RecordAccumulator),按分区聚合为批次(RecordBatch)。 - 后台
Sender线程从缓冲区拉取批次,发送到目标 Broker 的对应分区。 - 收到 Broker 确认后,触发回调(若配置)或返回结果。
生产者实现方式
单线程生产者
单线程生产者适用于消息量不大的场景,实现步骤如下:
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class KafkaSingleThreadProducer {
// 消息总数
private static final int MSG_COUNT = 5;
// 目标主题
private static final String TOPIC_NAME = "test-topic";
// 生产者实例
private static KafkaProducer<String, String> producer;
static {
// 初始化生产者配置
Properties props = initConfig();
producer = new KafkaProducer<>(props);
}
/**
* 初始化生产者配置
*/
private static Properties initConfig() {
Properties props = new Properties();
// 必选配置
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
// 可选配置
props.put(ProducerConfig.ACKS_CONFIG, "all"); // 最高可靠性确认
props.put(ProducerConfig.RETRIES_CONFIG, 3); // 失败重试3次
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); // 16KB批次
props.put(ProducerConfig.LINGER_MS_CONFIG, 100); // 最多等待100ms批量发送
return props;
}
/**
* 同步发送消息
* (通过Future.get()阻塞等待结果)
*/
public static void sendSync() {
try {
for (int i = 0; i < MSG_COUNT; i++) {
// 创建消息(主题、键、值)
ProducerRecord<String, String> record = new ProducerRecord<>(
TOPIC_NAME,
"sync-key-" + i,
"sync-value-" + i
);
// 同步发送(阻塞直到收到确认)
RecordMetadata metadata = producer.send(record).get();
System.out.printf("同步发送成功 - 分区:%d,偏移量:%d%n",
metadata.partition(), metadata.offset());
Thread.sleep(500); // 模拟业务间隔
}
} catch (InterruptedException | ExecutionException e) {
System.err.println("同步发送失败:" + e.getMessage());
} finally {
producer.close(); // 关闭生产者,释放资源
}
}
/**
* 异步发送消息
* (通过Callback回调处理结果,非阻塞)
*/
public static void sendAsync() {
try {
for (int i = 0; i < MSG_COUNT; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>(
TOPIC_NAME,
"async-key-" + i,
"async-value-" + i
);
// 异步发送,通过回调处理结果
producer.send(record, new Callback() {
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null) {
// 发送成功
System.out.printf("异步发送成功 - 分区:%d,偏移量:%d%n",
metadata.partition(), metadata.offset());
} else {
// 发送失败
System.err.println("异步发送失败:" + exception.getMessage());
}
}
});
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.err.println("线程中断:" + e.getMessage());
} finally {
producer.close();
}
}
public static void main(String[] args) {
// 测试同步发送
System.out.println("===== 同步发送 =====");
sendSync();
// 测试异步发送
System.out.println("\n===== 异步发送 =====");
sendAsync();
}
}
关键区别:
- 同步发送:通过
producer.send(record).get()阻塞等待结果,确保消息发送状态,但吞吐量较低。 - 异步发送:通过
Callback回调非阻塞处理结果,吞吐量更高,适合高并发场景。
多线程生产者
当消息量较大时,可使用多线程生产者提高发送效率。注意:KafkaProducer 是线程安全的,可被多个线程共享,无需为每个线程创建单独实例。
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class KafkaMultiThreadProducer {
private static final int MSG_COUNT = 10;
private static final String TOPIC_NAME = "test-topic";
private static KafkaProducer<String, String> producer;
static {
Properties props = initConfig();
producer = new KafkaProducer<>(props);
}
private static Properties initConfig() {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.ACKS_CONFIG, "1");
props.put(ProducerConfig.RETRIES_CONFIG, 2);
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy"); // 启用snappy压缩
return props;
}
/**
* 消息发送任务(实现Runnable)
*/
static class ProducerTask implements Runnable {
private ProducerRecord<String, String> record;
public ProducerTask(ProducerRecord<String, String> record) {
this.record = record;
}
public void run() {
producer.send(record, new Callback() {
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception != null) {
System.err.println("线程 " + Thread.currentThread().getId() + " 发送失败:" + exception.getMessage());
} else {
System.out.printf("线程 %d 发送成功 - 分区:%d,偏移量:%d%n",
Thread.currentThread().getId(),
metadata.partition(),
metadata.offset());
}
}
});
}
}
public static void main(String[] args) {
// 创建线程池(4个线程)
ExecutorService executor = Executors.newFixedThreadPool(4);
try {
for (int i = 0; i < MSG_COUNT; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>(
TOPIC_NAME,
"multi-key-" + i,
"multi-value-" + i
);
// 提交发送任务
executor.submit(new ProducerTask(record));
}
} finally {
executor.shutdown(); // 关闭线程池
producer.close(); // 关闭生产者
}
}
}
优势:
- 利用线程池并行处理发送任务,提高吞吐量。
- 共享单个
KafkaProducer实例,减少资源消耗(避免重复创建连接)。
自定义分区策略
Kafka 默认根据消息键(Key)的哈希值分配分区,若需自定义分区规则(如按业务类型分配),可实现 Partitioner 接口:
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
/**
* 自定义分区器:按消息值中包含的"order"或"log"关键词分配到不同分区
*/
public class CustomPartitioner implements Partitioner {
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
// 获取主题的分区总数
int numPartitions = cluster.partitionsForTopic(topic).size();
String valueStr = value.toString();
if (valueStr.contains("order")) {
// 包含"order"的消息分配到分区0
return 0 % numPartitions;
} else if (valueStr.contains("log")) {
// 包含"log"的消息分配到分区1
return 1 % numPartitions;
} else {
// 其他消息按Key哈希分配
return (key == null) ? 0 : Math.abs(key.hashCode() % numPartitions);
}
}
public void close() {
// 关闭资源(如需要)
}
public void configure(Map<String, ?> configs) {
// 读取配置(如需要)
}
}
使用自定义分区器:
在生产者配置中添加:
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.example.CustomPartitioner");
最佳实践
- 选择合适的发送方式:
- 同步发送:适用于对消息可靠性要求高、吞吐量要求低的场景(如金融交易)。
- 异步发送:适用于高吞吐量场景(如日志收集),通过回调处理异常。
- 优化性能:
- 启用批量发送(
batch.size+linger.ms)和压缩(compression.type)。 - 合理设置缓冲区大小(
buffer.memory),避免缓冲区满导致阻塞。
- 启用批量发送(
- 确保可靠性:
- 关键业务设置
acks=all+ 适当的重试次数(retries)。 - 配合回调函数处理发送失败,实现消息重试或落库补偿。
- 关键业务设置
- 线程安全:
KafkaProducer是线程安全的,多线程场景下应共享实例而非创建多个