KafkaNotes

第 03 章:环境搭建:从零跑起 KRaft 集群

zjc 于 2026-01-03 发布

这是《Kafka 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 学 Kafka 最忌讳只看不练。本章从下载安装包开始,把单机 KRaft 集群跑起来,再扩展成一台机器上的三节点“伪分布式”集群,为后续所有实验打底。

3.1 软件准备

软件 版本要求 说明
JDK 17+ Kafka 4.x 要求 Java 17 及以上,推荐 17 或 21 LTS
Kafka 4.x 官网下载二进制包,选 Scala 2.13 构建
操作系统 Linux/macOS/Windows 学习推荐 WSL2 或 Linux;Windows 原生用 .bat 脚本
内存 4GB+ 空闲 三节点伪集群建议 8GB

验证 JDK:

java -version

下载并解压(以 4.0.0 为例,请按需替换版本号):

tar -xzf kafka_2.13-4.0.0.tgz
cd kafka_2.13-4.0.0

解压后的目录结构:

bin/        可执行脚本(.sh)
config/     配置文件
libs/       依赖 jar
logs/       运行日志(server.log 等)
site-docs/  文档

3.2 Kafka 4.x 只有一种模式:KRaft

Kafka 3.x 之前依赖 ZooKeeper 管理元数据(Broker 注册、Controller 选举、topic 配置等)。这带来两个问题:

  1. 运维两套分布式系统,故障域更复杂;
  2. 元数据变更要先写 ZooKeeper 再通知 Broker,规模大时收敛慢。

KRaft(Kafka Raft)把元数据放进 Kafka 自己的 Raft 日志(__cluster_metadata)里,由 Controller 仲裁,Broker 作为学习者同步元数据。Kafka 4.0 起彻底移除 ZooKeeper。

所以本书从第一天就用 KRaft,这也是新集群的正确起点。

3.3 单节点 KRaft 启动

三步走:生成集群 ID -> 格式化存储目录 -> 启动服务。

# 1. 生成集群 ID
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"

# 2. 用默认配置格式化存储目录
bin/kafka-storage.sh format \
  --standalone \
  --config config/server.properties \
  --cluster-id $KAFKA_CLUSTER_ID

# 3. 启动
bin/kafka-server-start.sh config/server.properties

--standalone 会自动生成单节点控制器仲裁配置,非常适合本地学习。

验证进程是否就绪:

bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092 | head -n 5

能看到 Broker 返回 API 版本列表,说明启动成功。

Windows 用户对应命令:

bin\windows\kafka-storage.bat random-uuid
bin\windows\kafka-storage.bat format --standalone --config config\server.properties --cluster-id <上一步的ID>
bin\windows\kafka-server-start.bat config\server.properties

3.4 server.properties 关键配置解读

打开 config/server.properties,重点理解以下几行:

# 节点 ID,集群内唯一
node.id=1

# 控制器仲裁者列表(多节点集群必配)
# controller.quorum.voters=1@localhost:9093,2@localhost:9094,3@localhost:9095

# 监听地址:名称://主机:端口
listeners=PLAINTEXT://localhost:9092,CONTROLLER://localhost:9093

# 对外公布的地址,客户端用它连接
advertised.listeners=PLAINTEXT://localhost:9092

# 哪些监听器用于控制器通信
controller.listener.names=CONTROLLER

# 监听器安全协议映射
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_SSL:SASL_SSL,SASL_PLAINTEXT:SASL_PLAINTEXT

# 数据目录,多个目录用逗号分隔(可做多磁盘条带化)
log.dirs=/tmp/kafka-logs

# 自动创建 topic:学习期开,生产环境建议关
auto.create.topics.enable=true

三个易错点:

  1. listeners 是进程实际绑定的地址;advertised.listeners 是返回给客户端的地址。生产环境必须填 Broker 可被客户端访问的主机名/IP,写成 localhost 会导致远程客户端连不上;
  2. 修改 log.dirs 后必须重新执行 format(或清空目录),否则元数据不匹配会启动失败;
  3. 端口冲突是新手最常见的启动报错,多个节点共机时端口必须全部错开。

3.5 三节点伪分布式集群

在同一台机器上模拟生产集群,理解节点交互非常有效。规划如下:

节点 node.id broker 端口 controller 端口 数据目录
broker-1 1 19092 19093 /tmp/kraft-1
broker-2 2 29092 29093 /tmp/kraft-2
broker-3 3 39092 39093 /tmp/kraft-3

创建三个配置文件 config/server-1.propertiesserver-2.propertiesserver-3.properties,内容以节点 1 为例:

process.roles=broker,controller
node.id=1

controller.quorum.voters=1@localhost:19093,2@localhost:29093,3@localhost:39093

listeners=PLAINTEXT://:19092,CONTROLLER://:19093
advertised.listeners=PLAINTEXT://localhost:19092
controller.listener.names=CONTROLLER
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT

log.dirs=/tmp/kraft-1
num.partitions=3
default.replication.factor=3
min.insync.replicas=2
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2

节点 2、3 只需替换 node.id、两个端口和 log.dirs

格式化并依次启动:

KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"

for i in 1 2 3; do
  bin/kafka-storage.sh format \
    --config config/server-$i.properties \
    --cluster-id $KAFKA_CLUSTER_ID \
    --ignore-formatted
done

bin/kafka-server-start.sh -daemon config/server-1.properties
bin/kafka-server-start.sh -daemon config/server-2.properties
bin/kafka-server-start.sh -daemon config/server-3.properties

验证三台都活着:

bin/kafka-metadata-quorum.sh \
  --bootstrap-server localhost:19092 describe --status
bin/kafka-broker-api-versions.sh --bootstrap-server localhost:19092 \
  | grep -E '^localhost' 

describe --status 能看到 LeaderId 和三个 voter,说明 Raft 仲裁工作正常。

3.6 用 Docker Compose 一键起环境

如果不想手动管理目录和端口,可以用官方镜像快速起单节点:

services:
  kafka:
    image: apache/kafka:4.0.0
    container_name: kafka
    ports:
      - "9092:9092"
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"

启动:docker compose up -d。进入容器执行命令:docker exec -it kafka /opt/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092

3.7 停止与日志

优雅停止:

bin/kafka-server-stop.sh

排查启动问题时优先看:

3.8 常见启动故障速查

症状 常见原因 处理
启动秒退,日志提示 InconsistentClusterIdException 目录已被其他集群格式化 清空 log.dirs 后重新 format
Address already in use 端口冲突 换端口或杀掉占用进程
客户端连不上,Broker 日志正常 advertised.listeners 返回了错误地址 改成客户端可达的主机名/IP
集群hang在选举 controller.quorum.voters 配错 检查每个节点 ID@host:port
JMX 端口冲突 多节点共机未设 JMX_PORT 每个节点指定不同 JMX_PORT

本章小结

思考题

  1. 为什么 Kafka 一定要引入 advertised.listeners,只用 listeners 不行吗?
  2. 伪集群中 kill 掉某个 Broker,哪些 topic 分区会不可用?什么情况下仍然可用?
  3. log.dirs 配成多个目录有什么好处?Kafka 如何在多个目录间分配分区?