Kafka 已经成为现代分布式系统中不可或缺的核心组件,尤其是在微服务、事件驱动架构与实时流处理领域。作为 Kafka 使用的第一步,生产者(Producer) 负责将消息写入 Kafka,这个过程背后有哪些关键机制?如何实现高可靠、高性能的写入?
Kafka Producer 是 Kafka 客户端的一种,用于将消息以 Topic 的形式发送到 Kafka Broker 中的某个分区(Partition)。
它是 Kafka 架构中数据输入的入口,也是连接上游系统与 Kafka 流平台的桥梁。
在你写入一条 Kafka 消息的背后,其实包含以下几个步骤:
这是 Kafka 中的基本写入单元。
new ProducerRecord<>("topic", "key", "value");
topic
: 目标主题key
: 决定分区(可选)value
: 消息体Kafka 只接受字节数据,因此 key
和 value
需要先被序列化。Kafka 使用 Serializer
接口实现,比如最常用的:
key.serializer = org.apache.kafka.common.serialization.StringSerializer
value.serializer = org.apache.kafka.common.serialization.StringSerializer
你也可以自定义 JSON、Avro、Protobuf 等序列化方式,增强结构化能力与跨语言支持。
Kafka Producer 需要决定消息该写入哪个分区:
情况 | 分区策略 |
---|---|
指定 Key | 使用 Key 的哈希结果 mod 分区数 |
未指定 Key | 使用轮询方式(Round-Robin)均匀分配 |
自定义 Partitioner | 可根据业务逻辑自行决定分区 |
Kafka Producer 是高度可配置的,以下是常用配置项与建议:
参数 | 默认值 | 含义 |
---|---|---|
bootstrap.servers |
无 | Kafka Broker 地址列表 |
acks |
1 |
写入确认级别(见下) |
retries |
0 |
重试次数(发送失败) |
batch.size |
16384 |
单批消息最大大小(字节) |
linger.ms |
0 |
批消息等待时间(毫秒) |
compression.type |
none |
支持 gzip/snappy/lz4/zstd |
buffer.memory |
33554432 |
Producer 缓存总容量(字节) |
值 | 含义 | 适用场景 |
---|---|---|
0 |
不等待 Broker 响应 | 极端低延迟需求 |
1 |
等 Leader 副本确认 | 兼顾性能和基本可靠性 |
all (或 -1 ) |
等所有副本(ISR)确认 | 强一致性,金融/交易等场景推荐 |
Kafka Producer 支持两种发送方式:
producer.send(record, (metadata, exception) -> {
if (exception != null) {
// 失败处理
} else {
// 成功记录
}
});
producer.send(record).get();
为了提升 Kafka Producer 的吞吐性能,可采取以下几种方式:
使用 batch.size
+ linger.ms
控制:
batch.size
设置为 32KB~128KB;linger.ms
设置为 5~100ms,可适当延迟发送以合并更多消息。compression.type = lz4
retries = 5
retry.backoff.ms = 100
enable.idempotence = true
transactional.id = "my-transactional-producer"
关键点 | 建议 |
---|---|
ProducerRecord | 明确 Topic、Key、Value,决定分区行为 |
序列化 | 根据数据格式选择合适 Serializer |
分区策略 | 默认即可,自定义需慎重设计 |
写入可靠性 | acks=all + enable.idempotence=true 实现强一致性 |
性能优化 | 利用批量、压缩、异步发送提升性能 |
事务控制 | 需要 Exactly-once 可使用事务性 Producer |
Java版本
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "all");
props.put("retries", 3);
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 10; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>("demo-topic", "key" + i, "value" + i);
producer.send(record, (metadata, exception) -> {
if (exception == null) {
System.out.printf("Sent to partition %d with offset %d%n", metadata.partition(), metadata.offset());
} else {
exception.printStackTrace();
}
});
}
producer.close();
C++版本
#include
#include
#include
#include
bool run = true;
void stop(int sig) { run = false; }
class EventCb : public RdKafka::EventCb {
public:
void event_cb(RdKafka::Event &event) override {
if (event.type() == RdKafka::Event::EVENT_ERROR) {
std::cerr << "Kafka Error: " << event.str() << std::endl;
}
}
};
int main() {
std::signal(SIGINT, stop);
std::string brokers = "localhost:9092";
std::string topic = "demo-topic";
RdKafka::Conf *conf = RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL);
conf->set("bootstrap.servers", brokers, nullptr);
conf->set("dr_cb", nullptr, nullptr); // delivery report callback 可选
EventCb event_cb;
conf->set("event_cb", &event_cb, nullptr);
std::string errstr;
RdKafka::Producer *producer = RdKafka::Producer::create(conf, errstr);
if (!producer) {
std::cerr << "Producer creation failed: " << errstr << std::endl;
return 1;
}
while (run) {
std::string payload = "Hello Kafka from C++";
RdKafka::ErrorCode resp = producer->produce(
topic, RdKafka::Topic::PARTITION_UA,
RdKafka::Producer::RK_MSG_COPY,
const_cast<char *>(payload.c_str()), payload.size(),
nullptr, nullptr
);
if (resp != RdKafka::ERR_NO_ERROR) {
std::cerr << "Produce failed: " << RdKafka::err2str(resp) << std::endl;
}
producer->poll(0); // 触发回调
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
producer->flush(1000);
delete producer;
delete conf;
return 0;
}