Kafka的Java API 基本操作

在 Kafka 的生态体系中,Apache Kafka 原生 Java API 是开发者与 Kafka 进行交互的重要工具。通过该 API,开发者可以灵活地实现生产者数据的高效生产、消费者消息的准确消费,同时还能保障数据生产过程中的可靠性、顺序性、幂等性以及事务性。接下来,我们就详细介绍如何使用 Apache Kafka 原生 Java API 操作生产者和消费者,并深入探讨相关特性的实现。

一、环境搭建与依赖引入

在开始使用 Kafka 原生 Java API 之前,需要先搭建好 Java 开发环境,并在项目中引入 Kafka 相关依赖。

1.1 开发环境准备

确保本地已安装 JDK 8 或更高版本,可通过在命令行输入java -version验证 Java 环境是否正确安装。同时,准备好常用的 Java 开发工具,如 IntelliJ IDEA 或 Eclipse。

1.2 依赖引入

如果使用 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 生产者使用

2.1 基础生产者代码实现

下面是一个简单的 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();
        }
    }
}

在上述代码中:

  1. 首先配置了生产者的属性,包括 Kafka 集群的地址(BOOTSTRAP_SERVERS_CONFIG),以及消息键和值的序列化器(KEY_SERIALIZER_CLASS_CONFIG和VALUE_SERIALIZER_CLASS_CONFIG),这里使用了 Kafka 自带的字符串序列化器。
  2. 然后通过配置属性创建了KafkaProducer实例。
  3. 接着创建ProducerRecord对象,指定消息所属的主题、键和值。
  4. 使用producer.send(record)方法发送消息。
  5. 最后在finally块中关闭生产者,释放资源。

2.2 可靠性保证

Kafka 生产者可以通过配置acks参数来保证消息发送的可靠性:

  • acks=0:生产者发送消息后不需要等待任何确认,直接认为消息发送成功。这种方式吞吐量最高,但可靠性最低,消息可能会丢失。
  • acks=1:生产者发送消息后,只需要等待领导者副本确认接收即可。只要领导者副本将消息写入本地日志,就会向生产者发送确认响应。这种方式在吞吐量和可靠性之间取得了一定平衡,但如果领导者副本在确认消息后但追随者副本同步之前出现故障,可能会导致消息丢失。
  • acks=all(或 acks=-1):生产者发送消息后,需要等待所有 ISR(In - Sync Replicas,同步副本集合)中的副本都确认接收消息。只有当所有 ISR 中的副本都将消息写入本地日志后,领导者副本才会向生产者发送确认响应。这种方式提供了最高的可靠性,但牺牲了一定的吞吐量。

同时,生产者还可以通过设置retries参数来指定消息发送失败时的重试次数,进一步提高消息发送的可靠性。

2.3 顺序性保证

要保证消息的顺序性,可以将同一类消息发送到同一个分区。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());

这样,具有相同键的消息就会被发送到同一个分区,从而保证了消息的顺序性。

2.4 幂等性保证

Kafka 从 0.11.0.0 版本开始引入幂等性生产者,通过设置enable.idempotence参数为true,可以确保每条消息只会被写入 Kafka 一次,即使发生重试也不会导致消息重复。

properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);

启用幂等性后,Kafka 会为每个生产者分配唯一的 PID,并为每个分区维护一个序列号,以此来实现消息的幂等性。

2.5 事务保证

Kafka 支持事务性生产者,允许生产者在一个事务中发送多条消息,确保这些消息要么全部成功,要么全部失败。使用事务性生产者需要进行以下配置:

  1. 设置enable.idempotence为true,启用幂等性。
  2. 设置transactional.id,为生产者指定一个唯一的事务 ID。

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()回滚事务。

2.6 拦截器实现

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());

这样,每条发送的消息都会在值前添加当前时间戳。

2.7 序列化器实现

除了使用 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 消费者使用

3.1 基础消费者代码实现

以下是一个简单的 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();
        }
    }
}

在上述代码中:

  1. 配置消费者属性,包括 Kafka 集群地址、消费者组 ID(GROUP_ID_CONFIG),以及消息键和值的反序列化器。
  2. 创建KafkaConsumer实例。
  3. 使用consumer.subscribe(Collections.singletonList("test_topic"))方法订阅主题。
  4. 通过consumer.poll(Duration.ofMillis(100))方法拉取消息,并对拉取到的消息进行处理。
  5. 最后在finally块中关闭消费者。

3.2 消费者偏移量管理

消费者通过维护偏移量来记录已消费消息的位置。Kafka 提供了自动提交和手动提交两种偏移量管理方式:

  • 自动提交:通过设置enable.auto.commit为true(默认值),并配置auto.commit.interval.ms指定自动提交的时间间隔,消费者会定期自动提交偏移量。

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();
}

  • 同步提交:使用consumer.commitSync()方法,该方法会阻塞当前线程,直到偏移量提交成功或发生错误。
  • 异步提交:使用consumer.commitAsync()方法,该方法不会阻塞线程,提交操作会在后台执行。

通过以上对 Apache Kafka 原生 Java API 的详细介绍,相信你已经掌握了使用该 API 操作生产者和消费者的方法,以及如何实现数据生产过程中的各项特性和自定义功能。在实际项目中,可以根据具体需求灵活运用这些知识,构建高效、可靠的 Kafka 应用。

以上文章全面介绍了 Kafka 原生 Java API 的使用。如果你在实践中遇到问题,或希望对某部分内容有更深入的案例解析,欢迎随时与我交流。

你可能感兴趣的:(消息队列,kafka,java,后端)