Kafka 安装使用

280 阅读11分钟

一、Kafka 初印象

在当今大数据和分布式系统盛行的时代,数据的高效传输与处理至关重要。Kafka 作为一款高性能的分布式消息队列系统,犹如一颗璀璨的明星,在众多技术场景中发挥着关键作用。无论是电商系统中大量订单数据的实时处理,还是社交平台上用户动态的即时推送,Kafka 都能游刃有余地应对。它就像是一个高效的信息枢纽,连接着数据的生产者和消费者,让数据在系统中流畅地流动。接下来,让我们一同揭开 Kafka 的神秘面纱,深入了解它的强大之处。

二、安装 Kafka,搭建消息基石

安装前的准备

在安装 Kafka 之前,我们需要确保 Java 环境已经正确安装和配置。因为 Kafka 是基于 Java 开发的,所以 Java 环境是它运行的基础。你可以通过在命令行中输入 java -version 来检查 Java 是否已经安装。如果显示出 Java 的版本信息,那就说明 Java 已经成功安装;如果没有安装,你可以从Oracle 官网下载并安装最新版本的 Java。

下载 Kafka

Kafka 的官方下载地址是Apache Kafka Downloads。在下载页面,你会看到两种下载选项:源代码下载(Source Code)和二进制下载(Binary Downloads)。源代码下载需要你自己进行编译,过程相对复杂,适合对 Kafka 有深入定制需求的开发者;而二进制下载则是已经编译好的安装包,可以直接使用,对于大多数初学者和普通使用者来说,推荐使用二进制下载方式 。

以 Linux 系统为例,下载步骤如下:

  1. 打开终端,使用 wget 命令下载 Kafka 的二进制压缩包,例如:
#这里的版本号 3.4.0 和版本号 2.13 可以根据实际情况进行修改,选择你需要的版本。
wget https://dlcdn.apache.org/kafka/3.4.0/kafka_2.13-3.4.0.tgz
  1. 下载完成后,你会在当前目录下看到一个名为 kafka_2.13-3.4.0.tgz 的压缩包。

解压与配置

解压 Kafka 压缩包也很简单,在终端中执行以下命令:

tar -zxvf kafka_2.13-3.4.0.tgz

解压后,会生成一个名为kafka_2.13-3.4.0的目录,进入该目录:

cd kafka_2.13-3.4.0

接下来就是配置 Kafka 了,Kafka 的配置文件位于 config 目录下,主要的配置文件是 server.properties。我们需要修改一些关键参数:

  • broker.id:每个 Kafka broker 都需要有一个唯一的 ID,在单机环境下,一般设置为 0 即可。如果是集群环境,则每个 broker 的 ID 都不能相同。
  • listeners:指定 Kafka broker 监听的地址和端口,例如 listeners=PLAINTEXT://localhost:9092,表示监听本地的 9092 端口。
  • log.dirs:指定 Kafka 存储消息日志的目录,你可以根据自己的需求修改为合适的路径,比如 log.dirs=/data/kafka-logs
  • zookeeper.connect:指定 Zookeeper 的连接地址和端口,因为 Kafka 依赖 Zookeeper 来管理集群状态和元数据,默认值一般是 zookeeper.connect=localhost:2181

你可以使用文本编辑器打开 server.properties 文件进行修改,例如使用vim编辑器:

vim config/server.properties

修改完成后,保存并退出编辑器。

启动 Kafka

在启动 Kafka 之前,需要先启动 Zookeeper。因为 Kafka 依赖 Zookeeper 来进行分布式协调和管理。幸运的是,Kafka 安装包中已经包含了一个可以直接使用的 Zookeeper。在 Kafka 的安装目录下,打开终端,执行以下命令启动 Zookeeper:

bin/zookeeper-server-start.sh config/zookeeper.properties

启动 Zookeeper 后,会看到一系列的日志输出,表示 Zookeeper 正在启动并初始化。当看到类似[ZooKeeperServerMain] INFO binding to port 0.0.0.0/0.0.0.0:2181的日志时,说明 Zookeeper 已经成功启动并监听在 2181 端口。

然后,在另一个终端窗口中,同样进入 Kafka 的安装目录,执行以下命令启动 Kafka Server:

bin/kafka-server-start.sh config/server.properties

启动 Kafka Server 后,也会看到大量的日志输出。当看到[KafkaServer] INFO [KafkaServer id=0] started (kafka.server.KafkaServer)时,恭喜你,Kafka 已经成功启动了!

为了验证 Kafka 是否真的启动成功,你可以使用 Kafka 自带的命令行工具来创建一个简单的主题(topic),并发送和接收一些消息。在终端中执行以下命令创建一个名为test的主题:

bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test

这里--replication-factor 1表示副本因子为 1,--partitions 1表示分区数为 1。

创建主题后,可以使用以下命令向主题中发送消息:

bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test

然后在命令行中输入一些消息,比如 Hello, Kafka!,按回车键发送。

接着,使用以下命令从主题中消费消息:

bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning

如果能看到刚才发送的消息,那就说明 Kafka 已经成功安装并运行了。

三、Java 与 Kafka 的奇妙联动

在 Java 项目中使用 Kafka,就像是为项目赋予了强大的消息传递能力,让各个模块之间的通信更加高效和灵活。下面我们就来详细了解如何在 Java 中使用 Kafka 的各种消息类型。

引入 Kafka 依赖

在 Java 项目中使用 Kafka,首先需要引入 Kafka 的客户端依赖。如果你使用的是 Maven 项目,可以在 pom.xml 文件中添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>3.4.0</version>
    </dependency>
</dependencies>

这里的版本号3.4.0可以根据实际情况进行修改,以使用你需要的 Kafka 版本。

如果你使用的是 Gradle 项目,则在build.gradle文件中添加如下依赖:

implementation 'org.apache.kafka:kafka-clients:3.4.0'

生产者:消息的源头

Kafka 生产者负责将消息发送到 Kafka 集群中的指定主题(topic)。它就像是一个勤劳的快递员,不断地将各种消息投递到对应的目的地。

使用 Java 代码创建一个 Kafka 生产者,主要步骤如下:

  1. 配置生产者属性
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
Properties props = new Properties();
// Kafka集群地址
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
// 键的序列化器
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 值的序列化器
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 等待所有副本节点的应答
props.put(ProducerConfig.ACKS_CONFIG, "all");
// 消息发送失败后的重试次数
props.put(ProducerConfig.RETRIES_CONFIG, 3);
// 重试间隔时间
props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 100); 

这里的 bootstrap.servers 指定了 Kafka 集群的地址; key.serializer 和 value.serializer 分别指定了消息键和值的序列化器,因为 Kafka 在网络中传输的是字节数组,所以需要将消息进行序列化;acks 配置了生产者在等待多少个分区副本收到消息的情况下才会认为消息写入成功,acks = all 表示等待所有同步副本都确认收到消息,这能保证消息的可靠性,但会降低一些性能;retries 表示消息发送失败后的重试次数,retry.backoff.ms 表示重试的间隔时间 。

  1. 创建生产者实例
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
Producer<String, String> producer = new KafkaProducer<>(props);

这里创建了一个 KafkaProducer 实例,泛型 <String, String> 分别表示消息键和值的类型。

  1. 发送消息

Kafka 生产者发送消息有同步发送、异步发送(普通异步和带回调函数的异步发送)等方式。

  • 同步发送
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
try {
    ProducerRecord<String, String> record = new ProducerRecord<>("test", "key1", "message1");
    RecordMetadata metadata = producer.send(record).get();
    System.out.println("Message sent to partition " + metadata.partition() +
                       " at offset " + metadata.offset());
} catch (Exception e) {
    e.printStackTrace();
}

同步发送时,send 方法会返回一个 Future 对象,通过调用 get 方法会阻塞当前线程,直到消息发送成功或者抛出异常。ProducerRecord 用于创建一条消息,它包含了目标主题、消息键和消息值。

  • 普通异步发送
ProducerRecord<String, String> record = new ProducerRecord<>("test", "key2", "message2");
producer.send(record);
System.out.println("Message sent asynchronously.");

普通异步发送时,send 方法会立即返回,不会阻塞线程,消息会被发送到 Kafka 集群,但你无法得知消息是否发送成功。

  • 带回调函数的异步发送
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.RecordMetadata;
ProducerRecord<String, String> record = new ProducerRecord<>("test", "key3", "message3");
producer.send(record, new Callback() {
    @Override
    public void onCompletion(RecordMetadata metadata, Exception exception) {
        if (exception!= null) {
            exception.printStackTrace();
        } else {
            System.out.println("Message sent to partition " + metadata.partition() +
                               " at offset " + metadata.offset());
        }
    }
});
System.out.println("Message sent asynchronously with callback.");

带回调函数的异步发送方式,在消息发送完成(无论成功或失败)时,会调用回调函数 onCompletion 。在回调函数中,你可以处理发送结果或异常,这样既能实现异步发送的高效性,又能获取消息的发送状态。

  1. 关闭生产者
producer.close();

在生产者不再使用时,需要调用 close 方法关闭生产者,释放资源。

消费者:消息的归宿

Kafka 消费者负责从 Kafka 集群的指定主题中拉取消息,并进行相应的处理。它就像是一个忙碌的收件人,不断地接收并处理来自生产者的各种消息。

使用 Java 代码创建一个 Kafka 消费者,主要步骤如下:

  1. 配置消费者属性
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.util.Properties;
Properties props = new Properties();
// Kafka集群地址
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
// 消费者组ID
props.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group");
// 键的反序列化器
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 值的反序列化器
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 自动提交偏移量
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
// 自动提交偏移量的间隔时间
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000); 
// 当没有初始偏移量或当前偏移量无效时的策略,earliest表示从最早的消息开始消费
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); 

这里的 bootstrap.servers 同样指定了 Kafka 集群的地址;group.id指定了消费者组 ID,同一消费者组内的消费者会均衡消费主题中的消息; key.deserializer 和 value.deserializer 分别指定了消息键和值的反序列化器,用于将从 Kafka 集群接收到的字节数组转换为对象;enable.auto.commit 设置是否自动提交偏移量,偏移量用于记录消费者消费消息的位置,auto.commit.interval.ms指定了自动提交偏移量的间隔时间; auto.offset.reset 指定了当没有初始偏移量或当前偏移量无效时的策略,earliest 表示从最早的消息开始消费,latest 表示从最新的消息开始消费 。

  1. 创建消费者实例
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.Consumer;
Consumer<String, String> consumer = new KafkaConsumer<>(props);

这里创建了一个 KafkaConsumer 实例,泛型 <String, String> 同样表示消息键和值的类型。

  1. 订阅主题
import java.util.Arrays;
consumer.subscribe(Arrays.asList("test"));

这里使用 subscribe 方法订阅了名为 test 的主题,subscribe 方法可以接受一个主题列表,用于订阅多个主题。

  1. 拉取并处理消息
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import java.time.Duration;
while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
    for (ConsumerRecord<String, String> record : records) {
        System.out.println("Received message: " +
                           "topic = " + record.topic() +
                           ", partition = " + record.partition() +
                           ", offset = " + record.offset() +
                           ", key = " + record.key() +
                           ", value = " + record.value());
    }
}

使用poll方法从 Kafka 集群拉取消息,poll 方法会阻塞指定的时间(这里是 1 秒),等待新消息的到来。拉取到消息后,通过遍历 ConsumerRecords 来处理每条消息, ConsumerRecord 包含了消息的各种元数据信息,如主题、分区、偏移量、消息键和消息值等。

  1. 关闭消费者
consumer.close();

在消费者不再使用时,需要调用close方法关闭消费者,释放资源。

顺序消息:保证消息有序

在某些业务场景中,消息的顺序性至关重要。例如在金融交易系统中,订单的创建、支付、发货等消息必须按照顺序处理,否则可能会导致业务逻辑错误。Kafka 通过分区来保证消息的顺序性,即同一分区内的消息是有序的。

Kafka 实现顺序消息的原理是:生产者根据消息的 key 将消息发送到特定的分区,消费者从该分区按顺序消费消息。因为 Kafka 保证了同一分区内的消息是顺序写入和存储的,所以消费者按照顺序读取分区内的消息就能保证消息的顺序性。

使用 Java 代码实现顺序消息的发送和消费,示例如下:

  1. 发送顺序消息
// 假设我们根据订单ID作为消息的key,以保证同一订单的消息发送到同一分区
String orderId = "123456";
ProducerRecord<String, String> record = new ProducerRecord<>("order-topic", orderId, "订单创建消息");
producer.send(record);
record = new ProducerRecord<>("order-topic", orderId, "订单支付消息");
producer.send(record);
record = new ProducerRecord<>("order-topic", orderId, "订单发货消息");
producer.send(record);

这里通过将相同的 orderId 作为消息的 key,Kafka 会根据 key 的哈希值将这些消息发送到同一个分区,从而保证了同一订单的消息在分区内的顺序性。

  1. 消费顺序消息
consumer.subscribe(Arrays.asList("order-topic"));
while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
    for (ConsumerRecord<String, String> record : records) {
        System.out.println("Received order message: " +
                           "topic = " + record.topic() +
                           ", partition = " + record.partition() +
                           ", offset = " + record.offset() +
                           ", key = " + record.key() +
                           ", value = " + record.value());
        // 处理订单消息,按照顺序处理
        // 例如:如果是订单创建消息,进行创建订单的业务逻辑
        // 如果是订单支付消息,进行支付处理的业务逻辑
        // 如果是订单发货消息,进行发货处理的业务逻辑
    }
}

消费者订阅 order-topic 主题后,从分区中按顺序拉取消息并处理,这样就能保证消息的顺序性。在处理消息时,根据消息的内容和业务逻辑进行相应的处理,确保订单相关的业务流程按照正确的顺序执行。

四、总结

通过以上步骤,我们成功完成了 Kafka 的安装,并深入了解了如何使用 Java 代码操作 Kafka 的各种消息类型,包括普通消息和顺序消息 。Kafka 以其卓越的性能、高吞吐量和强大的分布式特性,为我们在大数据处理和实时消息传递领域提供了高效的解决方案。在实际项目中,你可以根据业务需求,灵活运用 Kafka 的这些特性,构建出稳定、可靠且高效的消息传递系统。