在 Kafka 的生态体系中,Apache Kafka 原生 Java API 是开发者与 Kafka 进行交互的重要工具。通过该 API,开发者可以灵活地实现生产者数据的高效生产、消费者消息的准确消费,同时还能保障数据生产过程中的可靠性、顺序性、幂等性以及事务性。接下来,我们就详细介绍如何使用 Apache Kafka 原生 Java API 操作生产者和消费者,并深入探讨相关特性的实现。
在开始使用 Kafka 原生 Java API 之前,需要先搭建好 Java 开发环境,并在项目中引入 Kafka 相关依赖。
确保本地已安装 JDK 8 或更高版本,可通过在命令行输入java -version验证 Java 环境是否正确安装。同时,准备好常用的 Java 开发工具,如 IntelliJ IDEA 或 Eclipse。
如果使用 Maven 管理项目依赖,在pom.xml文件中添加以下依赖:
org.apache.kafka
kafka-clients
3.5.1
如果使用 Gradle,在build.gradle文件中添加:
implementation 'org.apache.kafka:kafka-clients:3.5.1'
上述依赖引入了 Kafka 客户端库,为后续使用原生 Java API 操作 Kafka 奠定基础。
下面是一个简单的 Kafka 生产者示例代码,用于向指定主题发送消息:
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
public class KafkaProducerExample {
public static void main(String[] args) {
// 配置生产者属性
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
// 创建生产者实例
KafkaProducer producer = new KafkaProducer<>(properties);
try {
// 创建消息记录
ProducerRecord record = new ProducerRecord<>("test_topic", "key1", "message1");
// 发送消息
producer.send(record);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭生产者
producer.close();
}
}
}
在上述代码中:
Kafka 生产者可以通过配置acks参数来保证消息发送的可靠性:
同时,生产者还可以通过设置retries参数来指定消息发送失败时的重试次数,进一步提高消息发送的可靠性。
要保证消息的顺序性,可以将同一类消息发送到同一个分区。Kafka 的每个分区内的消息是有序的,因此通过自定义分区器,将具有相同特征的消息发送到固定分区,即可实现顺序性。下面是一个自定义分区器的示例:
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
public class CustomPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
// 根据消息键的哈希值进行分区
return Math.abs(key.hashCode()) % cluster.partitionCountForTopic(topic);
}
@Override
public void close() {}
@Override
public void configure(Map configs) {}
}
在生产者配置中添加自定义分区器:
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, CustomPartitioner.class.getName());
这样,具有相同键的消息就会被发送到同一个分区,从而保证了消息的顺序性。
Kafka 从 0.11.0.0 版本开始引入幂等性生产者,通过设置enable.idempotence参数为true,可以确保每条消息只会被写入 Kafka 一次,即使发生重试也不会导致消息重复。
properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
启用幂等性后,Kafka 会为每个生产者分配唯一的 PID,并为每个分区维护一个序列号,以此来实现消息的幂等性。
Kafka 支持事务性生产者,允许生产者在一个事务中发送多条消息,确保这些消息要么全部成功,要么全部失败。使用事务性生产者需要进行以下配置:
properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "my_transaction_id");
以下是一个使用事务性生产者的示例代码:
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.TransactionCallback;
import org.apache.kafka.clients.producer.TransactionContext;
import java.util.Properties;
public class KafkaTransactionalProducerExample {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "my_transaction_id");
KafkaProducer producer = new KafkaProducer<>(properties);
producer.initTransactions();
try {
producer.beginTransaction();
ProducerRecord record1 = new ProducerRecord<>("test_topic", "key1", "message1");
ProducerRecord record2 = new ProducerRecord<>("test_topic", "key2", "message2");
producer.send(record1);
producer.send(record2);
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
e.printStackTrace();
} finally {
producer.close();
}
}
}
在上述代码中,先调用initTransactions()初始化事务,然后通过beginTransaction()开启事务,发送多条消息后,使用commitTransaction()提交事务。如果在事务过程中发生异常,则调用abortTransaction()回滚事务。
Kafka 生产者支持拦截器,开发者可以自定义拦截器对消息进行预处理或后处理。下面是一个简单的拦截器示例,用于在消息发送前添加时间戳:
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.List;
import java.util.Map;
public class TimestampInterceptor implements ProducerInterceptor {
@Override
public ProducerRecord onSend(ProducerRecord record) {
// 在消息值前添加时间戳
return new ProducerRecord<>(record.topic(), record.partition(), record.timestamp(), record.key(), System.currentTimeMillis() + ": " + record.value());
}
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {}
@Override
public void close() {}
@Override
public void configure(Map configs) {}
}
在生产者配置中添加拦截器:
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TimestampInterceptor.class.getName());
这样,每条发送的消息都会在值前添加当前时间戳。
除了使用 Kafka 自带的序列化器,开发者还可以自定义序列化器。例如,自定义一个将自定义对象序列化为字节数组的序列化器:
import org.apache.kafka.common.serialization.Serializer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
class CustomObjectSerializer implements Serializer {
@Override
public void configure(Map configs, boolean isKey) {}
@Override
public byte[] serialize(String topic, T data) {
if (data == null) {
return null;
}
return data.toString().getBytes(StandardCharsets.UTF_8);
}
@Override
public void close() {}
}
在生产者配置中使用自定义序列化器:
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, CustomObjectSerializer.class.getName());
以下是一个简单的 Kafka 消费者示例代码,用于从指定主题消费消息:
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class KafkaConsumerExample {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "my_consumer_group");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer consumer = new KafkaConsumer<>(properties);
consumer.subscribe(Collections.singletonList("test_topic"));
try {
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord record : records) {
System.out.println("Received message: key = " + record.key() + ", value = " + record.value() + ", partition = " + record.partition() + ", offset = " + record.offset());
}
}
} finally {
consumer.close();
}
}
}
在上述代码中:
消费者通过维护偏移量来记录已消费消息的位置。Kafka 提供了自动提交和手动提交两种偏移量管理方式:
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 5000);
自动提交虽然方便,但可能会导致消息重复消费或漏消费。例如,在提交偏移量后但在消息处理完成前消费者发生故障,重启后会从已提交的偏移量位置开始消费,导致部分消息重复处理;而在处理完消息但未提交偏移量时消费者故障,则会出现漏消费。
try {
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord record : records) {
// 处理消息
}
consumer.commitSync();
}
} finally {
consumer.close();
}
try {
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord record : records) {
// 处理消息
}
consumer.commitAsync();
}
} finally {
consumer.close();
}
通过以上对 Apache Kafka 原生 Java API 的详细介绍,相信你已经掌握了使用该 API 操作生产者和消费者的方法,以及如何实现数据生产过程中的各项特性和自定义功能。在实际项目中,可以根据具体需求灵活运用这些知识,构建高效、可靠的 Kafka 应用。
以上文章全面介绍了 Kafka 原生 Java API 的使用。如果你在实践中遇到问题,或希望对某部分内容有更深入的案例解析,欢迎随时与我交流。