Kafka 安装与快速上手
先说不做什么
生产环境跑 Kafka 一般交给 K8s 上的 Strimzi Operator,或者由云厂商托管。但那两种方式都会把「节点怎么起、配置长什么样」藏起来,出问题时反而看不懂。所以这一篇老老实实在一台 Ubuntu 22.04 上装二进制、手写配置、手工启动,把 KRaft 模式跑通。
目标很具体:起一个单节点 Broker,建一个 3 分区的 Topic,用命令行生产者发几条消息,用消费者读回来,最后看一眼消费组的 lag。
3.3 起 KRaft(Kafka Raft,自带元数据共识,不再依赖 ZooKeeper)已可用于生产,3.7 是大多数人上手的稳定版本,4.0 直接删掉了 ZooKeeper 模式。新集群没有理由再选 ZooKeeper,本系列除第 4 篇的对比小节外,全部按 KRaft 写。
环境准备
三件事:JDK、Kafka 二进制、数据目录。
# Kafka 3.7 的 Broker 建议 JDK 17(4.0 起强制要求 17)
sudo apt update
sudo apt install -y openjdk-17-jdk curl
java -version
# 数据目录单独挂盘,别和系统盘混在一起
sudo mkdir -p /data/kafka/logs
sudo chown -R $USER:$USER /data/kafka
下载二进制包。注意 2.13 是编译 Kafka 用的 Scala 版本,和 Kafka 版本号无关,很多人第一次看会懵:
cd /opt
sudo curl -LO https://archive.apache.org/dist/kafka/3.7.2/kafka_2.13-3.7.2.tgz
sudo tar xf kafka_2.13-3.7.2.tgz
sudo mv kafka_2.13-3.7.2 kafka
sudo chown -R $USER:$USER /opt/kafka
export PATH=$PATH:/opt/kafka/bin
各目录的用途,先记一下后面经常要用:
| 路径 | 用途 |
|---|---|
bin/ | 所有 shell 脚本,kafka-topics.sh、kafka-console-producer.sh 都在这 |
config/kraft/ | KRaft 模式配置,server.properties 是单机模板 |
config/ | 老的 ZooKeeper 模式配置,4.0 已移除 |
libs/ | jar 包,排查版本冲突时会看 |
logs/ | 服务端日志(不是消息数据),默认就在这 |
/opt/kafka/logs 是 log4j 输出服务端日志的地方,消息数据由 log.dirs 指定。见过有人把 log.dirs 也指到这里,结果日志文件和数据文件混在一坨,出问题根本分不清,而且默认安装路径往往没有足够空间。
KRaft 单节点配置
编辑 config/kraft/server.properties,单机演示的最小配置如下:
############################# 角色与节点 #############################
# 单机同时承担 broker 和 controller 两个角色
process.roles=broker,controller
node.id=1
############################# 控制器 quorum #############################
# 单节点即自己投自己;多节点时列全部 controller
controller.quorum.voters=1@localhost:9093
############################# 网络 #############################
# 监听地址:broker 走 9092,controller 走 9093
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
# 对外通告地址,客户端拿到的就是这个,必须是客户端能访问的 IP
advertised.listeners=PLAINTEXT://192.168.1.10:9092
controller.listener.names=CONTROLLER
listener.security.protocol.map=PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
inter.broker.listener.name=PLAINTEXT
############################# 数据目录 #############################
log.dirs=/data/kafka/logs
############################# Topic 默认值 #############################
num.partitions=3
default.replication.factor=1
min.insync.replicas=1
############################# 内部主题 #############################
# 单机副本因子只能是 1,写成 3 会导致消费者组功能不可用
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
############################# 保留策略 #############################
log.retention.hours=168
log.retention.check.interval.ms=300000
log.segment.bytes=1073741824
几个参数值得单独说:
process.roles:broker、controller或两者都写。生产上建议分离部署,单机为了省资源才合在一起。node.id:集群内唯一,格式化后不能随便改,改了等于换了一台机器。advertised.listeners:客户端从 Broker 拿到的是这个地址。写成localhost就只能在机器本地连,远程客户端会报超时——这是新手第一大坑。listener.security.protocol.map:KRaft 内部通信也要走监听器,所以必须把CONTROLLER这一项映射上。offsets.topic.replication.factor:内部主题的副本因子不能超过当前 Broker 数,单机写 3 会直接导致消费组无法初始化。
格式化并启动
KRaft 模式没有 ZooKeeper 帮你初始化元数据,第一次启动前必须显式格式化存储目录:
cd /opt/kafka
# 生成集群唯一 ID,记下来,多节点要用同一个
KAFKA_CLUSTER_ID=$(bin/kafka-storage.sh random-uuid)
echo $KAFKA_CLUSTER_ID
# 格式化:写入 meta.properties 和初始元数据
bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c config/kraft/server.properties
格式化成功后输出类似 Formatting /data/kafka/logs with metadata.version 3.7-IV4。这里会创建 meta.properties,里面存着集群 ID 和节点 ID——别删这个文件,也别重复格式化。手滑重跑一次会提示目录已格式化,确实要强制重来才加 --ignore-formatted。
启动 Broker:
# 前台启动,方便看日志,验证阶段推荐
bin/kafka-server-start.sh config/kraft/server.properties
# 后台启动
bin/kafka-server-start.sh -daemon config/kraft/server.properties
看到 Kafka Server started 和 KafkaRaftServer nodeId=1 就说明起来了。再确认一下端口:
ss -lntp | grep -E '9092|9093'
# 查看 Broker 基本信息,能返回版本号说明 API 已经通了
bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092 | head -5
生产上别用 -daemon,交给 systemd 才管得住重启和开机自启。关键就两行:ExecStart=/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/kraft/server.properties,Restart=on-failure,再配个 LimitNOFILE=100000(Kafka 打开的文件句柄很多,默认 1024 会踩坑)。
第一个 Topic
# 创建 3 分区、1 副本的 Topic(单机副本只能为 1)
bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--create --topic quickstart \
--partitions 3 --replication-factor 1
# 确认创建结果:分区、Leader、副本、ISR 都会列出来
bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic quickstart
# 列出所有 Topic,包括内部主题
bin/kafka-topics.sh --bootstrap-server localhost:9092 --list
--describe 的输出是后面排障时看得最多的东西:
Topic: quickstart TopicId: 8Kq3v... PartitionCount: 3 ReplicationFactor: 1 Configs:
Topic: quickstart Partition: 0 Leader: 1 Replicas: 1 Isr: 1
Topic: quickstart Partition: 1 Leader: 1 Replicas: 1 Isr: 1
Topic: quickstart Partition: 2 Leader: 1 Replicas: 1 Isr: 1
Leader 是当前负责读写的 Broker,Replicas 是副本所在 Broker 列表,Isr 是同步中的副本。单机场景三列全是 1;集群里如果 Isr 比 Replicas 短,就说明有副本掉队了。
生产与消费
开一个终端当生产者(回车发送一条,Ctrl+C 退出):
bin/kafka-console-producer.sh --bootstrap-server localhost:9092 \
--topic quickstart \
--property parse.key=true \
--property key.separator=:
输入几行带 key 的消息,冒号前是 key:
order-1001:{"orderId":1001,"amount":99}
order-1002:{"orderId":1002,"amount":199}
order-1003:{"orderId":1003,"amount":29}
另开一个终端当消费者,--from-beginning 表示从最早的位置开始读:
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
--topic quickstart \
--from-beginning \
--property print.key=true \
--property print.partition=true \
--property print.offset=true
正常会看到三条消息依次打印,每行前面带着分区号和 offset。留意不同 key 会落进不同分区,而同一个 key 永远进同一个分区——这就是「分区内有序」的实现基础。
再验证一下顺序性:连续发 10 条 key 都是 order-1001 的消息,它们会全部进同一个分区,且 offset 连续递增。
不带这个参数时,消费者从 auto.offset.reset=latest 的默认值开始,只读启动之后新产生的消息。第一次玩的人经常因为这条以为消息丢了,其实是位点策略的问题。
查看消费组 lag
上面用的是临时消费者,没有组。重新用指定组消费一批,再来看位点:
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
--topic quickstart \
--group demo-group \
--from-beginning
消费完 Ctrl+C,然后查这个组的位点:
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--describe --group demo-group
输出长这样:
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID
demo-group quickstart 0 4 4 0 - - -
demo-group quickstart 1 3 3 0 - - -
demo-group quickstart 2 3 3 0 - - -
三列含义必须记牢,日常监控告警全围绕它们:
- CURRENT-OFFSET:消费组提交的位点,即「已经消费到哪」。
- LOG-END-OFFSET:分区当前最后一条消息的位置,即「生产到哪」。
- LAG:两者之差,即「还欠多少条没消费」。为 0 表示追平了,持续增长就是堆积。
消费者在线时,CONSUMER-ID、HOST 两列会显示具体是哪个实例在消费哪个分区;离线(比如 Ctrl+C 之后)就是 -,因为没有活跃成员了。
常见坑
- 启动报
No such file or directory: config/kraft/server.properties:3.7 的路径就是config/kraft/,4.0 改成了config/server.properties,按版本对一下。 - 远程连不上,本地
localhost能连:advertised.listeners还是localhost,改成真实 IP 后重启。 - 报
InconsistentClusterIdException:多节点格式化时用了不同的 cluster id,清空数据目录用同一个 ID 重新格式化。 - 消费组一直
Empty或消费不了:内部主题副本因子大于 Broker 数,把offsets.topic.replication.factor调成 1(单机)后重启。 - 端口起不来:
9093是 controller 端口,如果和别的服务冲突,改controller.quorum.voters和listeners里的端口要一起改。
小结
- 单机 KRaft 三步走:改
server.properties(process.roles=broker,controller)→kafka-storage.sh format→kafka-server-start.sh。 - 格式化只能做一次,集群 ID 记好;
advertised.listeners必须是客户端可达的地址。 - 建 Topic 用
kafka-topics.sh --create,单机副本因子只能 1,内部主题副本因子也要改成 1。 - 收发消息用
kafka-console-producer.sh/kafka-console-consumer.sh,带 key 时默认按 key 哈希分区。 - 判断消费健康就看
--describe --group:LAG = LOG-END-OFFSET 减 CURRENT-OFFSET,持续增长即堆积。
能跑通不等于会用。下一篇《核心使用》把命令行和参数系统过一遍:Topic 怎么改、生产者怎么写才不丢、消费者位点怎么提交、分区策略怎么自定义。