在大数据和消息队列领域,Apache Kafka 无疑是一颗璀璨的明星。作为一个分布式流处理平台,Kafka 以其高吞吐量、低延迟、可扩展性和容错性等优势,被广泛应用于日志收集、实时数据处理、流计算、数据集成等众多场景。无论是大型互联网公司,还是新兴的创业企业,Kafka 都在其技术栈中扮演着至关重要的角色,助力企业实现高效的数据处理和业务发展。
对于开发者而言,深入了解 Kafka 的最佳方式之一就是搭建本地环境进行学习和实践。通过搭建本地环境,我们可以在自己的电脑上模拟 Kafka 集群,熟悉 Kafka 的各种操作和功能,掌握如何使用 Kafka 客户端进行消息的发送和消费,为在实际项目中应用 Kafka 奠定坚实的基础。
使用 Docker 部署 Kafka 单节点非常简单,以下是具体步骤:
docker run -d --name kafka \
-p 9092:9092 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
confluentinc/cp-kafka:latest
上述命令中:
测试 Kafka:容器创建成功后,我们可以使用 Kafka 自带的命令行工具来测试 Kafka 是否正常工作。例如,创建一个名为test的主题:
docker exec -it kafka kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test
这里使用docker exec命令进入正在运行的kafka容器,并执行kafka-topics.sh脚本创建主题。--bootstrap-server localhost:9092指定 Kafka 服务器的地址和端口;--replication-factor 1表示副本因子为 1;--partitions 1表示分区数为 1;--topic test指定要创建的主题名为test。
部署 Kafka 多节点集群可以提高系统的可靠性和性能,以下是使用 Docker Compose 部署一个简单的三节点 Kafka 集群的步骤:
docker network create kafka-net
这个命令创建了一个名为kafka-net的 Docker 网络,后续我们的 Kafka 容器都将加入到这个网络中,使得它们可以通过容器名相互访问。
编写 docker-compose.yml 文件:在一个空目录下创建一个名为docker-compose.yml的文件,并编辑内容如下:
version: '3'
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest
container_name: zookeeper
ports:
- "2181:2181"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
networks:
- kafka-net
kafka1:
image: confluentinc/cp-kafka:latest
container_name: kafka1
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
KAFKA_LOG_DIRS: /var/lib/kafka/data
depends_on:
- zookeeper
networks:
- kafka-net
kafka2:
image: confluentinc/cp-kafka:latest
container_name: kafka2
ports:
- "9093:9093"
environment:
KAFKA_BROKER_ID: 2
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka2:9093
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9093
KAFKA_LOG_DIRS: /var/lib/kafka/data
depends_on:
- zookeeper
networks:
- kafka-net
kafka3:
image: confluentinc/cp-kafka:latest
container_name: kafka3
ports:
- "9094:9094"
environment:
KAFKA_BROKER_ID: 3
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka3:9094
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9094
KAFKA_LOG_DIRS: /var/lib/kafka/data
depends_on:
- zookeeper
networks:
- kafka-net
networks:
kafka-net:
external: true
在这个docker-compose.yml文件中:
启动集群:在包含docker-compose.yml文件的目录下,执行以下命令启动 Kafka 集群:
docker-compose up -d
-d参数表示以后台守护进程的方式启动所有服务。执行该命令后,Docker Compose 会根据docker-compose.yml文件的配置,依次启动 Zookeeper 和三个 Kafka 节点,稍等片刻,一个三节点的 Kafka 集群就部署完成了。你可以使用docker-compose ps命令查看所有服务的运行状态,确保所有容器都处于运行状态。
在 Java 项目中使用 Kafka,首先需要引入 Kafka 客户端依赖。org.apache.kafka:kafka-clients是 Kafka 官方提供的 Java 客户端库,它提供了一系列用于与 Kafka 集群进行交互的 API,包括生产者(Producer)、消费者(Consumer)和管理客户端(AdminClient)等。通过引入这个依赖,我们可以在 Java 代码中方便地实现消息的发送、接收以及对 Kafka 集群的管理操作,如创建主题、删除主题、查询集群元数据等。
如果你使用的是 Maven 项目,在pom.xml文件中添加如下依赖:
org.apache.kafka
kafka-clients
3.4.0
如果你使用的是 Gradle 项目,在build.gradle文件中添加如下依赖:
implementation 'org.apache.kafka:kafka-clients:3.4.0' // 可根据实际情况修改版本号
添加依赖后,Maven 或 Gradle 会自动下载kafka-clients及其相关的依赖包到本地仓库,并将其添加到项目的类路径中,以便我们在代码中使用。
在掌握了 Kafka 的本地环境搭建和 Java 环境配置后,接下来我们通过实战来深入了解 Kafka 的基本操作。我们将分别从 Kafka CLI 和 Java 代码两个版本来演示如何创建 Topic、发送消息和消费消息。
Kafka 提供了一系列命令行工具,方便我们对 Kafka 集群进行操作和管理。这些工具位于 Kafka 安装目录的bin目录下,通过命令行执行这些工具并传入相应的参数,就可以完成各种操作,如创建、删除和管理主题,发送和接收消息,管理消费者组等。
创建 Topic:
kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic my-topic
发送消息:
kafka-console-producer.sh --broker-list localhost:9092 --topic my-topic
执行上述命令后,会进入命令行输入模式,此时你可以逐行输入要发送的消息。例如,输入Hello, Kafka!,然后按回车键,这条消息就会被发送到my-topic主题中。输入完成后,按Ctrl + C组合键可以退出消息发送模式。这里--broker-list localhost:9092指定了 Kafka 代理服务器的地址和端口,--topic my-topic指定了要发送消息的主题。
消费消息:
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic my-topic --from-beginning
在 Java 代码中,我们使用org.apache.kafka:kafka-clients库提供的 API 来实现 Kafka 的操作。以 Maven 项目为例,在完成前文提到的依赖引入后,我们可以编写如下代码来创建生产者和消费者。
创建生产者:
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.common.serialization.StringSerializer;
import java.util.Properties;
public class KafkaProducerExample {
public static void main(String[] args) {
// 配置生产者属性
Properties props = new Properties();
// 指定Kafka broker地址,可以是多个,以逗号分隔
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
// 指定key的序列化器,将对象转换为字节数组,以便在网络中传输
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 指定value的序列化器
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 创建Kafka生产者实例
KafkaProducer producer = new KafkaProducer<>(props);
// 要发送的主题
String topic = "my-topic";
// 要发送的消息内容,这里构造了一条简单的消息,包含一个键和一个值
ProducerRecord record = new ProducerRecord<>(topic, "key1", "Hello, Kafka from Java!");
try {
// 发送消息,这是一个异步操作,producer会将消息发送到Kafka集群,并立即返回,不会阻塞当前线程
producer.send(record);
System.out.println("Message sent successfully");
} catch (Exception e) {
// 如果发送过程中出现异常,捕获并打印异常信息
e.printStackTrace();
} finally {
// 关闭生产者,释放资源,确保资源的正确关闭是良好的编程习惯,避免资源泄漏
producer.close();
}
}
}
创建消费者:
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class KafkaConsumerExample {
public static void main(String[] args) {
// 配置消费者属性
Properties props = new Properties();
// 指定Kafka broker地址
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
// 指定消费者组ID,同一消费者组内的消费者共同消费主题中的消息,通过组ID来标识属于同一个消费组
props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group");
// 指定key的反序列化器,将接收到的字节数组转换为对象
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 指定value的反序列化器
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 创建Kafka消费者实例
KafkaConsumer consumer = new KafkaConsumer<>(props);
// 订阅主题,可以订阅多个主题,这里使用Collections.singletonList方法将单个主题名封装成列表
consumer.subscribe(Collections.singletonList("my-topic"));
try {
while (true) {
// 拉取消息,设置超时时间为100毫秒,consumer.poll方法会从Kafka集群中拉取消息,并返回一个包含消息的ConsumerRecords对象
ConsumerRecords records = consumer.poll(Duration.ofMillis(100));
// 遍历接收到的消息
records.forEach(record -> {
// 打印消息的相关信息,包括主题、分区、偏移量、键和值
System.out.printf("Topic: %s, Partition: %d, Offset: %d, Key: %s, Value: %s%n",
record.topic(), record.partition(), record.offset(), record.key(), record.value());
});
}
} catch (Exception e) {
// 如果消费过程中出现异常,捕获并打印异常信息
e.printStackTrace();
} finally {
// 关闭消费者,释放资源
consumer.close();
}
}
}
在上述 Java 代码中,生产者通过KafkaProducer类的send方法发送消息,消费者通过KafkaConsumer类的poll方法拉取消息。同时,我们设置了一些关键的配置参数,如bootstrap.servers指定 Kafka 服务器地址,key.serializer和value.serializer用于生产者的序列化,key.deserializer和value.deserializer用于消费者的反序列化,group.id用于标识消费者组 。通过这些配置和代码,我们可以在 Java 程序中实现与 Kafka 集群的交互,进行消息的发送和消费。
通过本文,我们成功地完成了 Kafka 本地环境的搭建,包括使用 Docker 部署单节点和多节点的 Kafka 集群,配置 Java 项目的 Kafka 客户端依赖,以及通过 Kafka CLI 和 Java 代码进行创建 Topic、发送 / 消费消息的实战操作。这些基础知识和实践经验是我们深入学习 Kafka 的基石,为后续探索 Kafka 的高级特性,如分区策略、消息压缩、事务处理、监控与调优等,打下了坚实的基础。