在当今的微服务架构与大数据时代,系统之间的解耦、异步通信以及流量削峰成为了核心需求。Apache Kafka 作为一款开源的分布式事件流平台,凭借其高吞吐、低延迟、可扩展和持久化的特性,已然成为实时数据处理管道的首选方案。
本文将带你从零开始,深入理解 Kafka 的核心架构、部署运维、底层原理以及 Java 开发实战。
1. Kafka 概述与核心架构
1.1. 什么是消息队列
在介绍 Kafka 之前,我们先理解一下“消息队列”。简单来说,它就像是快递柜。生产者(Producer)把包裹(消息)存进去,消费者(Consumer)在合适的时候取出来。这种机制实现了“发布/订阅”模式,解决了以下痛点:
• 解耦:生产者和消费者不需要直接交互,互不影响。
• 异步:生产者发送消息后无需等待消费者处理,直接返回,提升响应速度。
• 削峰填谷:在流量洪峰时,消息先积压在队列中,消费者按照自己的处理能力慢慢消费,防止系统崩溃。
1.2. 消息队列两种模式
1.2.1. 点对点模式
一对一,消费者主动拉取数据,消息收到后消息清除
消息生产者生产消息发送到 Queue 中,然后消息消费者从 Queue 中取出并且消费消息。
消息被消费以后,Queue 中不再有存储,所以消息消费者不可能消费到已经被消费的消息。
Queue 支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
1.2.2. 发布/订阅模式
一对多,消费者消费数据之后不会清除消息
消息生产者(发布)将消息发布到 Topic 中,同时有多个消息消费者(订阅)消费该消息。
和点对点方式不同,发布到 Topic 的消息会被所有订阅者消费。
1.3. Kafka 的组成
Kafka 的架构设计非常经典,主要包含以下几个核心组件:
| 组件名称 | 说明 |
|---|---|
| Producer (生产者) | 消息的发送方,负责将数据推送到 Kafka 集群。 |
| Consumer (消费者) | 消息的接收方,从 Kafka 集群拉取数据进行处理。 |
| Broker (服务代理) | Kafka 集群中的单个服务器节点,负责消息的存储和转发。 |
| Topic (主题) | 消息的逻辑分类,类似于数据库中的表。 |
| Partition (分区) | Topic 的物理分片,一个 Topic 可以分为多个 Partition,分布在不同 Broker 上,是实现高吞吐的关键。 |
| Replica (副本) | 分区的备份,分为 Leader 和 Follower,用于保证数据的高可用性。 |
| Zookeeper | 协调服务(注:Kafka 3.x 后逐渐引入 KRaft 模式以去 Zookeeper 化),负责管理集群元数据和控制器选举。 |
1.4. Kafka 的基础架构
说明:
- 为了方便扩展,提高吞吐量,一个 Topic 分为多个 Partition 分区,每个分区存储不同的数据;
- 为了配合分区设计,提出消费者组的概念,每个消费者组有多个消费者,并行消费;
- 一个消费者消费一个 Topic 的一个 Partition;
- 提高可用性,为每个 Partition 增加若干个副本,副本不会提供消费者消费;
- 每个 Topic 中的消息,消费者只能消费一次;
- Topic 的主分区和从分区,主、从分区一定不会在同一个节点上;
2. Kafka 入门:部署与快速上手
2.1. 官网
Kafka 官网地址:kafka.apache.org/
Kafka 下载地址:kafka.apache.org/downloads
2.2. 环境准备
• Java 环境:JDK 1.8 或更高版本。
• Kafka 下载:前往 Apache Kafka 官网下载二进制包(如 )。
这里我们下载的是kafka_2.12-3.0.0版本,接下来我们部署一个三节点Kafka集群。
2.3. 安装部署
- 解压tar包,并修改Kafka目录名。
# 解压
tar -zxvf kafka_2.12-3.0.0.tgz -C /opt/module/
2. 编辑并修改config/server.properties配置文件。
# broker的全局唯一编号,不能重复
broker.id=0
# 删除topic功能使能
delete.topic.enable=true
# 处理网络请求的线程数量
num.network.threads=3
# 用来处理磁盘IO的现成数量
num.io.threads=8
# 发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
# 接收套接字的缓冲区大小
socket.receive.buffer.bytes=102400
# 请求套接字的缓冲区大小
socket.request.max.bytes=104857600
# kafka运行日志存放的路径
log.dirs=/opt/module/kafka_2.12-3.0.0/logs
# topic在当前broker上的分区个数
num.partitions=1
# 用来恢复和清理data下数据的线程数量
num.recovery.threads.per.data.dir=1
# segment文件保留的最长时间,超时将被删除
log.retention.hours=168
# 配置连接Zookeeper集群地址
zookeeper.connect=hadoop102:2181,hadoop103:2181,hadoop104:2181
3. 配置环境变量(这里我们将环境变量统一配置到/etc/profile.d/myEnv.sh文件中)。
#KAFKA_HOME
export KAFKA_HOME=/opt/module/kafka_2.12-3.0.0
export PATH=$PATH:$KAFKA_HOME/bin
4. 将Kafka分发到其他机器中。
5. 修改其他机器上的config/server.properties配置文件。
# 将broker.id配置,分别修改为 1 和 2,三台机器的borker.id不重复即可。
6. 集群启动与关闭
# 启动Kafka集群
./bin/kafka-server-start.sh --daemon config/server.properties
# 停止Kafka集群
./bin/kafka-server-stop.sh
2.4. Kafka的命令行操作
2.4.1. 查看topic
./bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --list
2.4.2. 创建topic
./bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --replication-factor 2 --partitions 2 --topic first
Created topic first.
参数说明:
- --topec:定义topic名;
- --replication-factor:定义副本数;
- --partitions:定义分区数;
2.4.3. 删除topic
./bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic first --delete
注意:
删除topic,需要配置文件server.properties中设置delete.topc.enable=true,否则,删除只是标记删除。
2.4.4. 查看topic详情
./bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic first --describe
# 返回如下内容:
Topic: first TopicId: HCmd897LR-i6wHuTEJ8oIQ PartitionCount: 2 ReplicationFactor: 2 Configs: segment.bytes=1073741824
Topic: first Partition: 0 Leader: 1 Replicas: 1,2 Isr: 1,2
Topic: first Partition: 1 Leader: 0 Replicas: 0,1 Isr: 0,1
2.4.5. 修改分区数
./bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --alter --topic first --partitions 3
重新查看topic详情:
./bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic first --describe
Topic: first TopicId: HCmd897LR-i6wHuTEJ8oIQ PartitionCount: 3 ReplicationFactor: 2 Configs: segment.bytes=1073741824
Topic: first Partition: 0 Leader: 1 Replicas: 1,2 Isr: 1,2
Topic: first Partition: 1 Leader: 0 Replicas: 0,1 Isr: 0,1
Topic: first Partition: 2 Leader: 0 Replicas: 0,2 Isr: 0,2
2.4.6. 发送消息
./bin/kafka-console-producer.sh --bootstrap-server hadoop102:9092 --topic first
> hello world
> aaa 121
2.4.7. 消费消息
./bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first --from-beginning
hello world
aaa 121
2.5. 快速实战:发送与接收消息
我们先不编写代码,直接使用Kafka自带的命令行工具,实现一个“Hello World”示例,来了解Kafka的基本使用。
- 创建用于发送与接收消息的Topic。
./bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --replication-factor 2 --partitions 2 --topic hello
Created topic hello.
2. 查看创建的Topic。
./bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --list
hello
3. 发送消息。
./bin/kafka-console-producer.sh --bootstrap-server hadoop102:9092 --topic hello
> hello world
> hello kafka
> ni hao!!!
4. 消费消息。
./bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first --from-beginning
hello world
hello kafka
ni hao!!!
注意:也可以先打开消费端,开启消费后,再发送数据,这样就可以看到实时发送与消费。
3. Kafka 架构深入:高性能的秘密
3.1. 为什么 Kafka 这么快
Kafka 的吞吐量可以达到百万级,主要得益于以下设计:
- 顺序写磁盘:Kafka 将消息追加写入到 Partition 的日志文件末尾。磁盘的顺序写性能远高于随机写,甚至可以媲美内存写入。
- 零拷贝:在数据发送过程中,利用 Linux 的 系统调用,数据直接从页缓存传输到网卡,避免了在内核态和用户态之间多次拷贝,极大降低了 CPU 开销。
- 批量发送与压缩:生产者可以将多条消息打包成一个批次发送,并支持 GZIP、Snappy 等压缩算法,减少网络 IO。
3.2. Kafka存储机制
Kafka 的消息是以日志的形式存储在磁盘上的。
- Topic — Partition — Segment:一个 Topic 包含多个 Partition,每个 Partition 物理上对应一个目录,目录下又分为多个 Segment(日志段)。
- Segment 文件:每个 Segment 包含实际消息数据、稀疏索引和时间戳索引。这种结构使得 Kafka 在查找特定消息时非常高效。
4. Kafka 分区策略:数据流转的交通规则
分区是 Kafka 实现高吞吐和并行处理的基石。生产者发送消息时,消息究竟会落入哪个分区?这由分区策略决定。合理的策略能避免数据倾斜,甚至保证消息的局部有序性。
4.1. 分区规则
Kafka 生产者的分区逻辑主要遵循以下流程:
- 指明 partition 的情况下,直接将指明的值直接作为 partiton 值;
- 没有指明 partition 值,但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值;
- 既没有 partition 值又没有 key 值的情况下,Kafka 会采用轮询策略(旧版本)或粘性分区策略(新版本),将消息均匀分发到各个分区,以实现负载均衡;
4.2. 分区好处
- 便于合理使用存储资源;
- 提高并行度;
4.3. 分区策略配置
- Kafka 默认使用的是 Range 策略;
- 配置 RoundRobin 策略,可以修改配置文件:
partition.assignment.strategy=org.apache.kafka.clients.consumer.RoundRobinAssignor
或者代码中直接设置,具体如下:
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, "org.apache.kafka.clients.consumer.RoundRobinAssignor");
4.4. 自定义分区
当默认策略无法满足业务需求时(例如需要根据用户 ID 的奇偶性分发,或者根据地理位置路由),我们可以实现自定义分区器。
只需实现 org.apache.kafka.clients.producer.Partitioner 接口:
public class CustomPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
// 自定义逻辑:例如根据 value 的长度取模
if (valueBytes == null) {
return 0;
}
return Math.abs(valueBytes.length) % numPartitions;
}
@Override
public void close() {}
@Override
public void configure(Map<String, ?> configs) {}
}
配置使用自定义分区器,只要在 Producer 配置中设置:properties.put("partitioner.class", "com.example.CustomPartitioner");。
5. 数据可靠性保证:消息去哪了
在生产环境中,我们最怕消息丢失。Kafka 通过多副本机制和确认机制来确保数据不丢。
5.1. 副本机制
每个分区都可以配置多个副本,分布在不同的 Broker 上。
- Leader:负责处理所有的读写请求。
- Follower:被动从 Leader 同步数据。
- ISR:与 Leader 保持同步的副本集合。只有 ISR 中的副本才有资格被选举为新的 Leader。
5.2. 确认机制
生产者发送消息后,需要等待 Broker 的确认。这个确认级别由 acks 参数控制;
| Ack配置 | 含义 | 可靠性 | 性能 | 场景 |
|---|---|---|---|---|
| 0 | 不等待确认 | 低 | 极高 | 日志采集,允许少量丢失 |
| 1 | 只要 Leader 写入成功即返回 | 中 | 高 | 一般业务,折中方案 |
| all | 等待 ISR 中所有副本写入成功 | 高 | 低 | 金融、订单等核心业务数据 |
最佳实践:为了达到消息写入所有同步副本,通常配合以下配置:
ack = all:确保消息写入所有同步副本;min.insync.replicas = 2:设置最小同步副本数。如果 ISR 副本数少于2个,写入会失败,防止数据在少数派节点少“裸奔”;replication.factor = 3:设置副本数为3,容忍最多1个节点故障;
6. 幂等性与事务:拒绝重复消息
即使有了可靠性保证,网络重试仍可能导致消息重复发送。为了解决这个问题,Kafka 引入了幂等性。
6.1. 什么是幂等性
幂等性是指无论生产者发送多少次相同的数据,Broker 最终只持久化一条。这对于防止因网络抖动导致的重复扣款、重复下单至关重要。
6.2. 实现原理
Kafka 的幂等性依赖于生产者 ID 和序列号。
- PID:每个生产者启动时会获得一个唯一的 ID。
- Sequence Number:生产者给每条消息分配一个单调递增的序列号。
- Broker 端去重:Broker 会检查
<PID, Partition, SequenceNumber>,如果发现序列号不连续或重复,就会拒绝或丢弃该消息。
6.3. 如何开启
只需要在 Producer 配置中添加发下:
enable.idempotence=true
开启后,Kafka 会自动将acks设置为all,并将重试次数设为无限次,确保消息既可靠又不重复。
6.4. 事务支持
如果需要跨分区、跨 Topic 的原子性操作(即“精确一次”语义),则需要使用 Kafka 事务。事务基于两阶段提交协议,保证一系列操作要么全部成功,要么全部失败。
// Java 事务示例
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(record1);
producer.send(record2);
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
}
7. Kafka API实战:Java与SpringBoot集成
7.1. Java 原生客户端集成
- 首先,在
pom.xml中引入依赖;
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.6.0</version>
</dependency>
2. 生产者代码示例;
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");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
// 发送消息
ProducerRecord<String, String> record = new ProducerRecord<>("test-topic", "key1", "Hello Java Kafka");
producer.send(record, (metadata, exception) -> {
if (exception == null) {
System.out.println("发送成功: " + metadata.offset());
}
});
producer.close();
3. 消费者代码示例;
7.2. SpringBoot 集成
- 引入依赖;
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
2. 修改application.yml配置文件;
spring:
kafka:
bootstrap-servers: localhost:9092
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
consumer:
group-id: my-group
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
auto-offset-reset: earliest
# 建议关闭自动提交,使用手动提交保证数据一致性
enable-auto-commit: false
3. 生产者发送消息;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
public void send(String msg) {
kafkaTemplate.send("test-topic", msg);
}
4. 消费者消费消息,或者说监听消息;
@KafkaListener(topics = "test-topic", groupId = "my-group")
public void listen(String message) {
System.out.println("收到消息: " + message);
// 业务逻辑处理...
}
8. Kafka 监控与运维
在生产环境中,监控是保障系统稳定运行的关键。我们需要关注 Broker 的健康状态、Topic 的积压情况(Lag)、网络流量等指标。
主流监控工具有:
-
Kafka Eagle
- 开源 Kafka 集群管理工具,功能强大;
- 支持多集群管理、Topic 管理、消费者组监控、消息积压告警等等;
- 提供可视化仪表盘,展示消息趋势和系统健康度;
-
Prometheus + Grafana
- Prometheus,负责采集 Kafka 的 JMX 指标;
- Grafana,负责数据可视化,可以导入现成的 Kafka 监控模板,展示极其精美的图表,如每秒消息注入量、分区分布图等;
-
Kafka Manager
- Yahoo 开源工具,主要用于集群管理和分区重新分配,界面相对简洁;