3. 生产者客户端:Producer API、发送模式、分区策略与序列化器
好,咱们今天聊聊 Kafka 生产者的核心。说白了,生产者就是往 Kafka 里写数据的那个角色。你想想看,整个数据管道能不能跑得稳,很大程度上取决于生产者写得对不对、快不快。
我记得刚接手第一个 Kafka 项目时,生产者的配置我调了一周。不是因为难,而是坑太多。今天我把这些经验掰开揉碎讲给你听。
3.1 Producer API 的基本用法
Kafka 的 Producer API 其实不复杂。核心就几个步骤:配参数、建客户端、发消息、关连接。
先看一个最基础的例子:
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");
Producer<String, String> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<>("my-topic", "key1", "hello kafka"));
producer.close();
嗯,这里要注意:bootstrap.servers 是必须的,它告诉客户端去哪找集群。我见过有人只配一个 broker 地址,结果那个 broker 挂了,客户端连不上。其实你配多个地址,客户端会自动发现集群里的其他节点。
3.2 发送模式:同步 vs 异步
Kafka 生产者支持三种发送模式。我按实际场景给你拆开讲。
| 发送模式 | 实现方式 | 适用场景 | 吞吐量 |
|---|---|---|---|
| 发后即忘 | producer.send(record) |
日志、监控等可丢失数据 | 最高 |
| 同步发送 | producer.send(record).get() |
关键交易、需要确认的场景 | 最低 |
| 异步发送 | producer.send(record, callback) |
大部分业务场景 | 高 |
发后即忘:就是只管发,不管结果。我早期做日志采集时用过,吞吐量确实高,但有一次集群抖动,丢了一批数据,排查了半天。所以,重要数据别用这个模式。
同步发送:调用 .get() 阻塞等待结果。我曾经在一个支付对账场景里用过,每条消息都要确认写入成功才发下一条。吞吐量确实低,但心里踏实。
异步发送:这是我最常用的模式。传一个 Callback 进去,发送线程不阻塞,等 broker 返回结果后再回调。你看这个例子:
producer.send(new ProducerRecord<>("orders", orderId, orderData),
(metadata, exception) -> {
if (exception != null) {
log.error("发送失败,订单ID: {}", orderId, exception);
// 这里可以重试或写入死信队列
} else {
log.info("发送成功,分区: {}, 偏移量: {}",
metadata.partition(), metadata.offset());
}
});
3.3 分区策略:数据该去哪
Kafka 的消息最终要落到某个分区上。分区策略决定了数据怎么分布。我画了一张图帮你理解:
Kafka 默认的分区策略是这样的:
- 如果消息指定了分区号,直接写入那个分区。我一般只在需要严格顺序的场景用这个,比如同一个订单的所有事件必须进同一个分区。
- 如果消息有 key,对 key 做哈希,然后对分区数取模。这样相同 key 的消息永远进同一个分区。我做过一个用户行为分析项目,用用户 ID 做 key,同一个用户的行为都在一个分区里,消费端处理起来特别方便。
- 如果既没分区号也没 key,Kafka 2.4 之前用轮询,之后用粘性分区。粘性分区会把一批消息攒起来发到同一个分区,减少网络开销。吞吐量能提升 30% 左右。
3.4 序列化器:数据怎么变成字节
Kafka 网络传输只认字节数组。序列化器就是把你的 Java 对象转成字节的东西。
Kafka 内置了几种常用序列化器:
| 序列化器 | 说明 | 适用场景 |
|---|---|---|
| StringSerializer | 字符串转 UTF-8 字节 | 文本数据、JSON 字符串 |
| IntegerSerializer | 整数转 4 字节 | 数值型 key |
| ByteArraySerializer | 直接传字节数组 | 已经序列化好的数据 |
| 自定义序列化器 | 实现 Serializer 接口 | Protobuf、Avro、自定义对象 |
我建议你用 Avro 或者 Protobuf。为什么?
- JSON 虽然可读性好,但体积大、解析慢。我有个项目用 JSON 传日志,一天几个 TB,光序列化就占了 30% 的 CPU。
- Avro 带 Schema,生产者和消费者可以约定好 Schema,数据格式变了也能兼容。我后来把那个日志项目改成 Avro,CPU 降了一半,带宽也省了 40%。
自定义序列化器也不难,实现 Serializer<T> 接口就行:
public class OrderSerializer implements Serializer<Order> {
@Override
public byte[] serialize(String topic, Order data) {
// 把 Order 对象转成字节数组
// 可以用 Protobuf、Avro 或者自己拼字节
return data.toByteArray();
}
}
嗯,到这里生产者客户端的主要内容就讲完了。总结一下:API 的用法要熟,发送模式按场景选,分区策略决定了数据分布,序列化器影响性能和兼容性。这些点在实际项目中一个都绕不开。