一、Kafka 简介与核心概念
1.1 什么是 Kafka?
Apache Kafka 是一个分布式消息队列系统,由 LinkedIn 开发,后捐给 Apache 基金会。它的核心作用是解耦数据生产者和消费者 ,就像现实生活中的邮局:
- 生产者:寄信的人(你的Java程序)
- Kafka:邮局(暂存、分类、转发信件)
- 消费者:收信的人(MongoDB写入程序)
1.2 Kafka 的核心作用
| 作用 | 说明 | 比喻 |
|---|---|---|
| 缓冲 | 生产者和消费者速度不匹配时暂存数据 | 邮局的信件暂存架 |
| 解耦 | 生产者和消费者互不感知对方存在 | 寄信人不需要知道谁收信 |
| 削峰填谷 | 应对突发流量,防止系统崩溃 | 双十一快递先堆在邮局 |
| 持久化 | 数据可保存一段时间,消费者随时来取 | 信件保存7天,随时可取 |
1.3 Kafka 核心术语
| 术语 | 解释 | 类比 |
|---|---|---|
| Topic(主题) | 消息的分类,类似数据库的表 | 不同类型的信件(平信、挂号信) |
| Partition(分区) | Topic 的分片,提高并行度 | 多个分拣口同时处理信件 |
| Producer(生产者) | 发送消息的程序 | 寄信人 |
| Consumer(消费者) | 接收消息的程序 | 收信人 |
| Broker(代理) | Kafka 服务器节点 | 邮局的分局 |
| Offset(偏移量) | 消息在分区中的唯一编号 | 信件的邮政编码+流水号 |
二、ZooKeeper 简介与原理
2.1 什么是 ZooKeeper?
Apache ZooKeeper 是一个分布式协调服务,用来管理集群状态。如果把 Kafka 比作邮局,ZooKeeper 就是邮局的管理系统:
- 记录哪个邮局分局(Broker)在运行
- 记录哪个分局负责哪个片区的信件(Leader选举)
- 通知大家谁出故障了
2.2 ZooKeeper 的核心原理
ZooKeeper 采用 ZAB 协议(ZooKeeper Atomic Broadcast),保证数据一致性:
text
客户端\] → \[Leader节点\] → \[Follower节点1
↘ [Follower节点2]
↘ [Follower节点3]
- Leader:处理所有写请求,广播给其他节点
- Follower:处理读请求,参与Leader选举
- 奇数节点:必须奇数个(3、5、7),才能选举出多数派
2.3 ZooKeeper 在 Kafka 中的作用
| 作用 | 存储内容 | 类比 |
|---|---|---|
| Broker注册 | 记录哪些Kafka服务器活着 | 邮局分局名录 |
| Topic元数据 | 主题有哪些分区,副本在哪 | 信件分拣规则表 |
| Leader选举 | 选出一个分区的主副本 | 谁负责这个片区的信件 |
| 消费进度 | 记录消费者组读到哪里了 | 谁读到哪封信了 |
三、Kafka 配置方案
3.1 方案一:Standalone 模式(无 ZooKeeper,Kafka 3.x+)
这是 Kafka 3.0 引入的 KRaft (Kafka Raft)模式 ,不需要 ZooKeeper,适合开发测试。
3.1.1 配置步骤
server.properties(单节点KRaft模式)
properties
# 必须配置 process.roles
process.roles=broker,controller # 同时充当broker和控制器
# 节点ID
node.id=1
# 控制器监听器
controller.listener.names=CONTROLLER
# 监听器配置
listeners=PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
advertised.listeners=PLAINTEXT://localhost:9092
# 控制器选举配置(单节点)
controller.quorum.voters=1@localhost:9093
# 数据目录
log.dirs=/tmp/kafka-logs
# 基本配置
num.partitions=3
default.replication.factor=1
offsets.topic.replication.factor=1
3.1.2 初始化与启动
bash
# 1. 生成集群ID
./bin/kafka-storage.sh random-uuid
# 输出类似:fRg4QvF8T8mUz0kL1nX2pQ
# 2. 格式化存储目录(使用 --standalone 参数)
./bin/kafka-storage.sh format -t fRg4QvF8T8mUz0kL1nX2pQ \
-c config/server.properties --standalone
# 3. 启动Kafka
./bin/kafka-server-start.sh config/server.properties
3.1.3 优缺点
| 优点 | 缺点 |
|---|---|
| 配置简单,无需维护ZooKeeper | 无高可用,单点故障 |
| 资源占用少 | 不能水平扩展 |
| 适合开发测试 | 生产环境不推荐 |
3.2 方案二:ZooKeeper 模式(传统集群)
这是 Kafka 经典模式,需要独立 ZooKeeper 集群,适合生产环境。
3.2.1 集群规划(以3节点为例)
| 节点 | IP | Kafka Broker ID | ZooKeeper 角色 |
|---|---|---|---|
| node1 | 192.168.1.101 | 1 | Leader/Follower |
| node2 | 192.168.1.102 | 2 | Leader/Follower |
| node3 | 192.168.1.103 | 3 | Leader/Follower |
3.2.2 ZooKeeper 配置
在每个节点上:
bash
# 创建数据目录
mkdir -p /data/zookeeper
zookeeper.properties
properties
tickTime=2000
dataDir=/data/zookeeper
clientPort=2181
initLimit=10
syncLimit=5
# 集群节点配置
server.1=192.168.1.101:2888:3888
server.2=192.168.1.102:2888:3888
server.3=192.168.1.103:2888:3888
# 自动清理
autopurge.snapRetainCount=3
autopurge.purgeInterval=1
创建 myid 文件(各节点不同)
bash
# node1
echo "1" > /data/zookeeper/myid
# node2
echo "2" > /data/zookeeper/myid
# node3
echo "3" > /data/zookeeper/myid
启动 ZooKeeper
bash
# 所有节点执行
./bin/zookeeper-server-start.sh -daemon config/zookeeper.properties
# 查看状态
./bin/zookeeper-shell.sh localhost:2181 stat
3.2.3 Kafka Broker 配置
server.properties(以node1为例)
properties
# Broker唯一ID(集群内唯一)
broker.id=1
# 监听地址
listeners=PLAINTEXT://192.168.1.101:9092
advertised.listeners=PLAINTEXT://192.168.1.101:9092
# 日志目录
log.dirs=/data/kafka-logs
# ZooKeeper 连接
zookeeper.connect=192.168.1.101:2181,192.168.1.102:2181,192.168.1.103:2181
# 主题配置(高可用设置)
default.replication.factor=3 # 副本数=节点数
min.insync.replicas=2 # 最小同步副本
num.partitions=6 # 分区数
# 内部主题配置
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2
# 网络线程(根据CPU调整)
num.network.threads=8
num.io.threads=8
其他节点只需修改 broker.id 和 listeners 中的 IP。
启动 Kafka
bash
# 所有节点执行
./bin/kafka-server-start.sh -daemon config/server.properties
3.2.4 验证集群
bash
# 查看Broker列表
./bin/zookeeper-shell.sh localhost:2181 ls /brokers/ids
# 输出:[1,2,3]
# 创建测试主题
./bin/kafka-topics.sh --bootstrap-server 192.168.1.101:9092 \
--create --topic test-cluster \
--partitions 3 --replication-factor 3
# 查看主题详情
./bin/kafka-topics.sh --bootstrap-server 192.168.1.101:9092 \
--describe --topic test-cluster
# 输出示例:
# Topic: test-cluster Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
# Topic: test-cluster Partition: 1 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1
# Topic: test-cluster Partition: 2 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2
Isr (In-Sync Replicas) 等于 Replicas 说明集群健康。
四、Kafka 与 MongoDB 集成
4.1 整体架构
text
Java Producer\] → \[Kafka Topic\] → \[Java Consumer\] → \[MongoDB
↑ ↓
生成数据 批量写入
4.2 配置参数详解
关键配置文件 conf.properties说明
properties
# Kafka 连接配置
kafka.bootstrap.servers=192.168.1.101:9092,192.168.1.102:9092
kafka.topic=pm.mo.update # 主题名,类似数据库表名
# 生产者性能配置
kafka.producer.hasKey=false # 无key:轮询发到各分区,性能最好
# true:相同key发同分区,保证顺序
kafka.producer.acks=1 # 确认机制:1(leader确认),all(所有副本确认)
kafka.producer.batch.size=65536 # 批次大小(字节),攒够一批才发
kafka.producer.linger.ms=10 # 等待时间(ms),攒批提高吞吐
kafka.producer.compression=snappy # 压缩:snappy/lz4 提高网络效率
# 消费者性能配置
kafka.consumer.max.poll.records=2000 # 一次拉取最大条数
kafka.consumer.fetch.max.bytes=52428800 # 一次拉取最大字节(50MB)
# MongoDB 连接配置
mongodb.hosts=192.168.1.201:27017 # MongoDB地址
mongodb.database=iot_data # 数据库名
mongodb.collection=sensor_data # 集合名
mongodb.batch.size=1000 # 批量写入条数
4.3 参数对性能的影响
| 参数 | 配置建议 | 性能影响 |
|---|---|---|
| hasKey=false | ✅ 最优 | 无哈希计算,负载均衡最好 |
| batch.size | 65536 (64KB) | 越大吞吐越高,但延迟增加 |
| linger.ms | 5-10ms | 等待攒批,可提升30%+吞吐 |
| compression | snappy | 减少网络传输,CPU换带宽 |
| max.poll.records | 2000-5000 | 一次处理更多,减少网络往返 |
| mongodb.batch.size | 1000 | 批量写入,可提升5-10倍性能 |
4.4 消费者代码优化示例
java
// 消费者主循环 - 批量写入MongoDB
while (true) {
// 从Kafka拉取一批消息
ConsumerRecords<String, byte[]> records = consumer.poll(100);
// 攒批写入MongoDB
List<Document> docs = new ArrayList<>();
for (ConsumerRecord<String, byte[]> record : records) {
// 反序列化为MongoDB Document
Document doc = Document.parse(new String(record.value()));
docs.add(doc);
// 攒够一批就写入
if (docs.size() >= batchSize) { // batchSize=1000
collection.insertMany(docs);
docs.clear();
}
}
// 写入最后剩余的一批
if (!docs.isEmpty()) {
collection.insertMany(docs);
}
// 手动提交offset(确保至少一次处理)
consumer.commitSync();
}
4.5 性能基准对比
| 测试场景 | TPS (docs/sec) | 说明 |
|---|---|---|
| YSCB (C++直接写MongoDB) | 30万+ | C++极致优化,无中间件 |
| Kafka Producer (无消费者) | 14-15万 | Kafka自身吞吐能力 |
| Java Consumer (纯计数) | 12-13万 | 消费者代码本身效率 |
| Java Consumer + MongoDB | 10-12万 | 完整链路,含反序列化和写入 |
| 集群模式 + 多消费者 | 20-30万 | 多节点并行,需要MongoDB分片 |
4.6 常见性能瓶颈排查
bash
# 1. 检查Kafka生产者性能
./bin/kafka-producer-perf-test.sh \
--topic test-perf --num-records 1000000 \
--record-size 1024 --throughput -1 \
--producer-props bootstrap.servers=localhost:9092
# 2. 检查消费者纯读性能(注释掉MongoDB写入)
# 修改代码,只计数不写入,重新编译测试
# 3. 检查磁盘IO
iostat -x 1
# 查看 await 和 %util,如果 >20ms 或 >80% 说明磁盘慢
# 4. 检查MongoDB写入性能
mongostat --host your-mongodb-host
# 查看 insert 列,看实际写入速度
4.7 调优总结
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 生产者TPS低 | 批次太小/未压缩 | 增大batch.size,启用compression |
| 消费者TPS低 | 单条写入 | 改为insertMany批量写入 |
| MongoDB写入慢 | 索引太多/磁盘慢 | 优化索引,换SSD |
| 整体TPS不达标 | 单节点瓶颈 | 上集群,多消费者并行 |
五、方案选型建议
5.1 环境选择
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 开发测试 | Standalone (KRaft) | 配置简单,资源占用少 |
| 小型生产 | 3节点ZooKeeper + 3节点Kafka | 高可用,可容忍单节点故障 |
| 大型生产 | 5节点ZooKeeper + N节点Kafka | 更高可用,可水平扩展 |
5.2 性能预期
| 配置 | 预期TPS | 适用场景 |
|---|---|---|
| Standalone + 单消费者 | 10-12万 | 开发测试,小型项目 |
| 3节点集群 + 3消费者 | 20-30万 | 中型生产,IoT数据 |
| 集群 + MongoDB分片 | 50万+ | 大型互联网,实时分析 |
六、常见问题解答
Q1: Standalone 模式和集群模式性能差多少?
A: 集群模式因数据复制,生产者TPS会降低5-10%,但消费者TPS可通过多消费者提升。
Q2: ZooKeeper 一定要奇数节点吗?
A: 是的,奇数节点(3、5、7)才能选出多数派Leader,偶数节点(如2个)无法容错。
Q3: 为什么我的TPS只有5万?
A: 检查是否每条都写MongoDB,改为批量写入可提升5-10倍;检查acks=all是否导致性能下降。
Q4: hasKey=false 和 true 怎么选?
A: 不需要保证顺序就用false(性能最好);需要相同key顺序处理用true。
Q5: 消费者如何做到至少一次处理?
A: 处理完数据后再commitSync(),确保处理成功才提交offset。
七、文档总结
本文档从零开始介绍了:
- Kafka:分布式消息队列,起缓冲解耦作用
- ZooKeeper:分布式协调服务,管理Kafka集群状态
- 两种配置方案 :
- Standalone模式(无ZK,适合开发)
- ZooKeeper集群模式(生产环境)
- 与MongoDB集成:Java消费者批量写入MongoDB
- 性能调优:关键参数解释和优化建议
通过本配置,可以实现:
- 数据生产:Java程序发送消息到Kafka
- 缓冲解耦:Kafka暂存消息,削峰填谷
- 数据消费:消费者从Kafka拉取数据,批量写入MongoDB
- 性能可达:10-30万 TPS(取决于硬件和配置)
最终目标:构建一个高吞吐、可扩展的实时数据处理管道。