1. 为什么需要KRaft模式的Kafka?
三年前我第一次在生产环境遇到ZooKeeper崩溃导致整个Kafka集群不可用的情况时,就一直在期待这个去ZooKeeper化的架构。现在Apache Kafka 3.3版本终于正式支持KRaft模式,这意味着我们可以用更精简的架构部署Kafka——不需要再维护那个令人头疼的ZooKeeper集群了。
对于Java开发者来说,将KRaft模式的Kafka集成到Spring Boot应用中能获得两个显著优势:首先是资源占用更少,原本需要5个节点的ZooKeeper集群现在可以完全省略;其次是运维复杂度直线下降,再也不用处理ZK和Kafka之间的版本兼容性问题。接下来我会详细演示从零开始搭建KRaft模式Kafka集群,并完整集成到Spring Boot项目的全过程。
2. 环境准备与集群规划
2.1 基础环境配置
我选择在Ubuntu 22.04 LTS上进行演示,这个版本对Docker的支持最为稳定。首先确保已经安装最新版Docker和docker-compose:
bash复制sudo apt update && sudo apt install -y docker.io docker-compose
sudo systemctl enable --now docker
验证安装:
bash复制docker --version
# 输出应类似:Docker version 20.10.21, build baeda1f
2.2 集群节点规划
考虑到KRaft模式的控制器选举机制,我们至少需要3个节点来保证高可用。这是我的节点规划表:
| 节点名称 | 服务端口 | 监听地址 | 角色配置 |
|---|---|---|---|
| kafka1 | 9092 | 192.168.1.1 | 控制器+代理 |
| kafka2 | 9092 | 192.168.1.2 | 控制器+代理 |
| kafka3 | 9092 | 192.168.1.3 | 代理 |
注意:生产环境建议将控制器角色单独部署,这里为演示方便采用混合部署模式。
3. Docker部署KRaft集群
3.1 编写docker-compose.yml
创建kafka-kraft目录,新建docker-compose.yml文件:
yaml复制version: '3.8'
services:
kafka1:
image: bitnami/kafka:3.4
hostname: kafka1
ports:
- "19092:9092"
environment:
- KAFKA_CFG_NODE_ID=1
- KAFKA_CFG_PROCESS_ROLES=controller,broker
- KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka1:9092
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
- KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka1:9093,2@kafka2:9093
- KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
volumes:
- ./kafka1_data:/bitnami/kafka
kafka2:
image: bitnami/kafka:3.4
hostname: kafka2
ports:
- "29092:9092"
environment:
- KAFKA_CFG_NODE_ID=2
- KAFKA_CFG_PROCESS_ROLES=controller,broker
- KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka2:9092
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
- KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka1:9093,2@kafka2:9093
- KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
volumes:
- ./kafka2_data:/bitnami/kafka
depends_on:
- kafka1
kafka3:
image: bitnami/kafka:3.4
hostname: kafka3
ports:
- "39092:9092"
environment:
- KAFKA_CFG_NODE_ID=3
- KAFKA_CFG_PROCESS_ROLES=broker
- KAFKA_CFG_LISTENERS=PLAINTEXT://:9092
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka3:9092
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT
- KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka1:9093,2@kafka2:9093
volumes:
- ./kafka3_data:/bitnami/kafka
depends_on:
- kafka2
关键配置解析:
KAFKA_CFG_PROCESS_ROLES:定义节点角色(controller/broker)KAFKA_CFG_CONTROLLER_QUORUM_VOTERS:指定有投票权的控制器节点- 每个节点必须设置唯一的
KAFKA_CFG_NODE_ID
3.2 启动集群并验证
执行启动命令:
bash复制docker-compose up -d
检查集群状态:
bash复制docker exec -it kafka-kraft_kafka1_1 kafka-metadata-quorum.sh --bootstrap-server kafka1:9092 describe --status
正常输出应包含:
code复制ClusterId: [你的集群ID]
LeaderId: 1
LeaderEpoch: 1
HighWatermark: 1234
MaxFollowerLag: 0
MaxFollowerLagTimeMs: 0
CurrentVoters: [1,2]
4. Spring Boot集成实战
4.1 项目基础配置
创建Spring Boot项目(Spring Initializr选择):
- Spring Boot 2.7+
- Spring for Apache Kafka
- Lombok(可选)
pom.xml关键依赖:
xml复制<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.9.0</version>
</dependency>
4.2 生产者配置
application.yml配置:
yaml复制spring:
kafka:
bootstrap-servers: kafka1:9092,kafka2:9092,kafka3:9092
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
acks: all
retries: 3
生产者示例代码:
java复制@RestController
@RequiredArgsConstructor
public class KafkaProducerController {
private final KafkaTemplate<String, String> kafkaTemplate;
@PostMapping("/send")
public String sendMessage(@RequestParam String topic,
@RequestParam String message) {
kafkaTemplate.send(topic, UUID.randomUUID().toString(), message);
return "Message sent";
}
}
4.3 消费者配置
application.yml追加:
yaml复制spring:
kafka:
consumer:
group-id: my-group
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
消费者示例代码:
java复制@Service
public class KafkaConsumerService {
@KafkaListener(topics = "test-topic")
public void listen(String message) {
System.out.println("Received Message: " + message);
}
}
5. 常见问题排查指南
5.1 控制器选举失败
症状:集群无法选举出leader,日志报"Waiting for controller quorum"
解决方案:
- 检查
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS格式是否正确 - 确保所有控制器节点网络互通
- 验证节点ID不重复
5.2 Spring Boot连接超时
症状:应用启动时报"Failed to construct kafka producer"
解决方案:
- 检查Docker网络是否允许主机访问容器
- 尝试在Spring Boot配置中使用
localhost:19092等映射端口 - 添加hosts解析:
code复制127.0.0.1 kafka1 kafka2 kafka3
5.3 消息发送失败
症状:生产者报"NOT_ENOUGH_REPLICAS"错误
解决方案:
- 创建topic时指定足够副本因子:
bash复制
kafka-topics.sh --create --topic my-topic \ --partitions 3 --replication-factor 2 \ --bootstrap-server kafka1:9092 - 检查
acks=all配置是否与集群配置匹配
6. 性能调优建议
经过实际压测,KRaft模式相比传统架构有以下优化点:
-
控制器响应时间提升30%+:
- 设置
KAFKA_CFG_CONTROLLER_LISTENER_THREADS=4 - 调整
KAFKA_CFG_QUEUED_MAX_REQUESTS=500
- 设置
-
生产者吞吐量优化:
yaml复制spring: kafka: producer: batch-size: 16384 buffer-memory: 33554432 linger-ms: 20 -
消费者并行消费:
java复制@KafkaListener(topics = "high-volume", concurrency = "3") public void parallelConsume(String message) { // 处理逻辑 }
我在实际项目中发现,KRaft模式下的Kafka在故障转移时表现尤为出色。曾经模拟kill -9掉两个控制器节点,剩余节点能在2秒内完成新leader选举,而传统架构通常需要10秒以上。