☰
Mac 上从零跑通 Kafka:brew 安装、启动、消息收发与关闭全链路
2026/10/15 11:46:55 网站建设 项目流程

1. 为什么在 Mac 上跑 Kafka 值得单独写一篇

很多人第一次在 Mac 上折腾 Kafka,都会经历一个相似的循环:搜到一篇教程,照着敲命令,brew install kafka跑完,然后卡在"怎么启动"这一步。网上大部分资料要么是 Linux 服务器环境的,要么默认你已经懂了 ZooKeeper 和 Kafka 的关系,要么直接甩给你一个bin/kafka-server-start.sh就完事,结果你发现路径根本对不上。

这篇东西就是把我自己在 Mac 上从零跑通 Kafka 的完整链路摊开讲一遍。核心关键词就几个:brew 安装 kafka、启动 kafka、创建 topic、发送消息、读取消息、关闭 kafka 和 zookeeper。看起来是六个独立动作,实际上它们是一条完整的验证链路——装完不启动等于没装,启动了不建 topic 没法发消息,发完不消费你根本不知道通没通。

适合谁看?三类人最合适。第一类是刚接触消息队列、想在本地快速搭个环境做实验的后端新手;第二类是做数据管道、需要本地复现生产链路的数据开发;第三类是想拿 Kafka 当练手项目、但被环境配置劝退的学生或者转行者。如果你已经在服务器上玩得很溜,这篇对你价值不大,但如果你想在 Mac 本地搞一套干净、可反复重启、不污染系统的环境,下面的内容应该能帮你省掉至少两三个小时的试错。

先说一个反直觉的结论:在 Mac 上用 brew 装 Kafka,最麻烦的从来不是安装本身,而是版本变化带来的启动方式差异。早期版本强依赖 ZooKeeper,你得先起 ZK 再起 Kafka;新版本引入了 KRaft 模式,可以不要 ZooKeeper 了。这两种模式在 brew 安装的包里同时存在,配置文件也混在一起,如果你不搞清楚自己用的是哪种,启动命令就会互相打架。所以下面我会把两种模式都讲清楚,你按自己的版本对号入座。

2. brew 安装 Kafka 的完整过程与版本选择逻辑

2.1 安装前的环境确认

动手之前先确认两件事,能避免后面 80% 的莫名其妙报错。

第一,确认你的 brew 是可用的。终端里敲:

brew --version

能正常输出版本号就行。如果提示 command not found,那得先把 brew 装好,这一步网上资料很多,不展开。

第二,确认 Java 环境。Kafka 是跑在 JVM 上的,没有 Java 它起不来。敲:

java -version

这里有个坑要提前说:Kafka 对 Java 版本是有要求的,不是随便一个 Java 都能跑。较新的 Kafka 版本通常要求 Java 8 或 Java 11 以上,部分新版本甚至推荐 Java 17。如果你机器上是很老的 Java 8 之前的版本,或者装了一堆版本导致java -version输出的和你以为的不一样,启动时就会报UnsupportedClassVersionError这类错误。我自己的做法是明确指定一个稳定的 JDK,比如用 brew 装一个:

brew install openjdk@17

然后用java -version确认当前默认的就是它。如果你机器上有多个 JDK,可以用JAVA_HOME环境变量来锁定,避免 Kafka 启动脚本抓到一个不兼容的版本。

2.2 brew 安装命令与它到底装了什么

确认环境没问题后,安装命令本身很简单:

brew install kafka

但我想让你理解这条命令背后发生了什么,因为后面所有路径问题都和它有关。brew 装 Kafka 的时候,会做几件事:

  • 把 Kafka 的二进制包解压到 brew 的 cellar 目录下,通常是/opt/homebrew/Cellar/kafka/<版本号>/(Apple Silicon 芯片)或者/usr/local/Cellar/kafka/<版本号>/(Intel 芯片)。
  • 在/opt/homebrew/bin/或/usr/local/bin/下创建一堆软链接,比如kafka-server-start、kafka-topics、kafka-console-producer这些命令,让你在任何目录下都能直接调用。
  • 同时会装一个 ZooKeeper 的依赖(如果你装的是依赖 ZK 的版本),因为老版本 Kafka 把 ZK 当作必需组件。

这里的关键点是:brew 装完之后,Kafka 的配置文件不在你当前目录,而在 cellar 里的config目录下。很多人启动失败就是因为用了相对路径去找配置文件,结果找不到。正确的做法是先用brew --prefix kafka拿到安装根目录:

brew --prefix kafka

假设输出是/opt/homebrew/opt/kafka,那配置文件就在/opt/homebrew/opt/kafka/libexec/config/下面。这个路径记牢,后面启动命令要用。

2.3 版本差异:ZooKeeper 模式与 KRaft 模式怎么选

这是整篇里最需要你动脑子的地方。Kafka 从 2.8 开始引入 KRaft 模式(也就是不依赖 ZooKeeper 的元数据管理方式),到 3.x 逐渐成熟,4.0 之后基本把 ZooKeeper 边缘化了。但 brew 装出来的版本,两种模式的配置文件可能都在。

怎么判断你装的是哪种?看配置文件目录里有没有server.properties和kraft相关的目录。一般来说:

  • 如果config目录下有server.properties,并且里面写着zookeeper.connect=...,那这是 ZooKeeper 模式。
  • 如果config目录下有kraft/server.properties或者server.properties里写着process.roles=broker,controller,那这是 KRaft 模式。

我的建议是:如果你只是想本地快速验证,优先用 KRaft 模式,因为它少起一个进程,链路更短,出错点更少。但如果你要复现生产环境(很多公司生产还在用 ZK 模式),那就老老实实把 ZooKeeper 也跑起来。下面两种模式我都会给完整命令。

3. 启动 Kafka 与 ZooKeeper 的两种模式实操

3.1 ZooKeeper 模式:先起 ZK 再起 Kafka

ZooKeeper 模式的核心逻辑是:Kafka 的元数据(比如有哪些 topic、分区分布、broker 列表)都存在 ZooKeeper 里,所以 ZK 必须先活着,Kafka 才能启动。

第一步,启动 ZooKeeper。brew 装 Kafka 时通常也带了 ZK 的启动脚本,命令是:

zookeeper-server-start /opt/homebrew/opt/kafka/libexec/config/zookeeper.properties

注意这里的配置文件路径,一定要用brew --prefix kafka得到的真实路径,别照抄我的,因为你的芯片类型和 brew 安装位置可能不同。这条命令执行后,终端会刷一堆日志,看到类似binding to port 0.0.0.0:2181就说明 ZK 起来了。这个终端窗口不要关,关了 ZK 就停了。

第二步,另开一个终端窗口,启动 Kafka:

kafka-server-start /opt/homebrew/opt/kafka/libexec/config/server.properties

同样,看到日志里出现[KafkaServer id=0] started这样的字样,就说明 Kafka broker 起来了。这时候你有两个终端窗口在跑服务,一个 ZK 一个 Kafka。

这里有个实操心得:如果你嫌每次开两个窗口麻烦,可以用-daemon参数让它们后台运行。比如:

zookeeper-server-start -daemon /opt/homebrew/opt/kafka/libexec/config/zookeeper.properties kafka-server-start -daemon /opt/homebrew/opt/kafka/libexec/config/server.properties

但后台运行有个代价——你看不到日志了。一旦启动失败,排查起来更费劲。所以我的习惯是:第一次配置环境时前台跑,确认没问题了,之后再用后台模式。这个顺序别搞反。

3.2 KRaft 模式:单进程启动的简洁路径

KRaft 模式下不需要 ZooKeeper,Kafka 自己同时扮演 broker 和 controller 两个角色。启动前通常需要先格式化存储目录(相当于初始化元数据),命令类似:

kafka-storage random-uuid

这条命令会生成一个集群 ID,然后拿这个 ID 去格式化:

kafka-storage format -t <刚才生成的UUID> -c /opt/homebrew/opt/kafka/libexec/config/kraft/server.properties

格式化完成后,启动 Kafka:

kafka-server-start /opt/homebrew/opt/kafka/libexec/config/kraft/server.properties

看到started日志就成功了。KRaft 模式最大的好处是你只需要管一个进程,关闭的时候也只关一个。但要注意,不同版本的 KRaft 配置文件名和路径可能不一样,有的在config/kraft/下,有的直接就是config/server.properties改的。你得自己ls一下配置目录确认。

3.3 启动失败的常见原因排查

启动这一步是新手翻车最多的地方,我把几个高频问题列出来,你对着排查。

现象可能原因解决方向
报UnsupportedClassVersionErrorJava 版本不兼容换 Java 8/11/17,用JAVA_HOME锁定
报Address already in use端口被占用(9092 或 2181)用lsof -i :9092查进程并处理
找不到配置文件路径写错用brew --prefix kafka确认真实路径
ZK 起来了但 Kafka 连不上 ZKZK 没完全启动就起了 Kafka等 ZK 日志稳定后再起 Kafka
日志刷Connection refusedZK 挂了或端口不对检查zookeeper.connect配置

端口占用这个特别常见。比如你之前跑过一次 Kafka 没关干净,9092 还被占着,新的一次就起不来。查端口占用的命令:

lsof -i :9092

拿到 PID 之后kill -9 <PID>干掉它。但要注意,别把系统里其他正常服务的进程误杀了,看清楚进程名再动手。

4. 创建 Topic 与消息收发的完整验证链路

4.1 创建 Topic 时分区和副本怎么定

Kafka 起来了,但你不能直接发消息,得先有 topic。创建 topic 的命令是:

kafka-topics --create --topic my-test-topic --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1

这里几个参数值得展开说,因为它们直接决定你后面实验的效果。

--partitions 3表示这个 topic 分 3 个分区。分区是 Kafka 并行处理的基本单位,分区越多,理论上吞吐越高,但管理开销也越大。本地实验我一般给 3 个,既能观察到分区分配的效果,又不至于太复杂。

--replication-factor 1表示副本数为 1。副本是为了容错,生产环境通常设 3,但本地你只有一个 broker,设成大于 1 会直接报错,因为副本数不能超过 broker 数量。这是新手常踩的坑:照抄生产配置写--replication-factor 3,结果本地只有一个 broker,创建直接失败。

创建完可以验证一下:

kafka-topics --list --bootstrap-server localhost:9092

能看到my-test-topic就说明建好了。再看细节:

kafka-topics --describe --topic my-test-topic --bootstrap-server localhost:9092

这条命令会列出每个分区的 leader、replicas、isr 信息。本地单 broker 的情况下,三个分区的 leader 都是 0 号 broker,这是正常的。

4.2 用控制台生产者发送消息

发消息用控制台生产者:

kafka-console-producer --topic my-test-topic --bootstrap-server localhost:9092

执行后终端会进入一个交互模式,光标闪烁等你输入。你敲一行字回车,这条消息就发出去了。比如输入:

hello kafka second message third message

每敲一行回车,就发一条。这里有个细节:控制台生产者默认把每一行当作一条独立消息,key 是 null。如果你需要指定 key,得加参数--property parse.key=true,然后用key:value的格式输入。key 的作用是决定消息进哪个分区——相同 key 的消息会进同一个分区,这对需要保证局部顺序的场景很重要。

发送的时候终端不会有明显反馈,这是正常的,别以为没发出去。要确认发没发成功,得靠消费者。

4.3 用控制台消费者读取消息

另开一个终端,跑消费者:

kafka-console-consumer --topic my-test-topic --bootstrap-server localhost:9092 --from-beginning

--from-beginning这个参数很关键,它表示从最早的消息开始读。如果不加,消费者默认只读它启动之后新产生的消息,你之前发的就看不到了。新手经常犯的错就是不加这个参数,然后纳闷"我明明发了消息怎么消费不到"。

正常的话,你会看到刚才发的三条消息依次打印出来。这时候你回到生产者终端再发一条,消费者终端会实时打印出来,这就证明整条链路通了。

消费的时候还有几个实用参数:

  • --group my-group:指定消费者组。同一组内的消费者会分摊分区,不同组之间互不影响。做实验时如果不指定,Kafka 会生成一个随机组名。
  • --property print.key=true:把消息的 key 也打印出来,方便调试分区逻辑。
  • --max-messages 5:只消费 5 条就退出,适合脚本化验证。

我个人的习惯是:验证链路时用--from-beginning加--max-messages,读几条确认没问题就自动退出,不用手动 Ctrl+C。这样干净利落。

4.4 消息顺序与分区的实测观察

这里分享一个我实际做过的观察,能帮你理解分区和顺序的关系。我建了一个 3 分区的 topic,然后用带 key 的方式发消息,key 分别是a、b、c,各发多条。消费的时候你会发现:同一个 key 的消息总是按发送顺序出现,但不同 key 之间的相对顺序是不保证的。

原因就在于相同 key 会被哈希到同一个分区,而 Kafka 只保证单分区内有序。这个特性在做订单、用户行为这类需要按实体保序的场景时特别重要。如果你不加 key,消息会轮询分配到各分区,那全局顺序就彻底没了。

想直观看到分区分配,可以在生产者里加--property parse.key=true,消费者里加--property print.key=true --property print.partition=true,这样每条消息属于哪个分区一目了然。这个实验花五分钟做一遍,比看十篇理论文章都管用。

5. 关闭 Kafka 与 ZooKeeper 的正确姿势

5.1 前台运行时怎么优雅关闭

如果你是按前面说的前台方式启动的,关闭就简单:在跑 Kafka 的终端窗口按Ctrl+C。Kafka 会收到中断信号,走一个 shutdown hook,把正在处理的事情收尾,然后退出。看到日志里出现shut down completed之类的字样,就说明关干净了。

然后再到跑 ZooKeeper 的窗口按Ctrl+C。顺序很重要:先关 Kafka,再关 ZooKeeper。反过来先关 ZK 的话,Kafka 会因为它依赖的元数据存储没了而报一堆错,虽然最终也能关掉,但日志很难看,而且极端情况下可能留下不一致的状态。

5.2 后台运行时如何找到并停止进程

如果你用了-daemon后台启动,就没法用 Ctrl+C 了,得靠进程管理。先找到进程:

ps aux | grep kafka ps aux | grep zookeeper

或者更精准地用 jps(JVM 进程查看工具):

jps -l

它会列出所有 Java 进程和主类名,Kafka 的进程主类通常是kafka.Kafka,ZooKeeper 的是org.apache.zookeeper.server.quorum.QuorumPeerMain。拿到 PID 后:

kill <PID>

先发普通 kill 信号,给它优雅退出的机会。如果等了几秒还没退,再用kill -9 <PID>强杀。强杀是最后手段,因为它不给进程收尾的机会,可能留下锁文件或者未刷盘的日志,下次启动可能报错。我一般会先普通 kill,等 5 秒,不行再 -9。

5.3 关闭后如何确认真的关干净了

关完之后别急着走,确认一下端口释放了:

lsof -i :9092 lsof -i :2181

如果没有任何输出,说明端口空了,进程确实退了。如果还有输出,说明进程没死透,得回去继续处理。

还有一个容易忽略的点:Kafka 关闭后会在数据目录留下一些临时文件,正常情况下下次启动会自动清理,但如果你强杀过,可能残留。数据目录一般在配置文件里的log.dirs指定,默认可能是/opt/homebrew/var/lib/kafka-logs或者/tmp/kafka-logs之类。如果下次启动报奇怪的错误,可以去这个目录看看,必要时手动清理(但清理意味着数据丢失,本地实验无所谓,生产千万别乱删)。

6. 反复重启环境时的几个实战经验

6.1 数据要不要每次清空

本地做实验,我建议你想清楚每次重启是要保留数据还是从零开始。Kafka 的数据默认是持久化的,你关了再开,之前的 topic 和消息还在。这有时候是好事(可以接着上次的实验),有时候是坏事(残留的 topic 干扰新实验)。

如果你想每次从干净状态开始,可以在关闭后手动删掉数据目录,或者干脆在启动前用一个全新的log.dirs路径。我自己的做法是准备两套配置:一套指向持久目录用于长期实验,一套指向临时目录用于一次性验证。这样切换起来很清爽。

6.2 用脚本把启动和关闭串起来

当你反复重启十几次之后,一定会想写脚本。我给你一个思路,不写死路径,用brew --prefix动态获取:

#!/bin/bash KAFKA_HOME=$(brew --prefix kafka)/libexec start() { zookeeper-server-start -daemon $KAFKA_HOME/config/zookeeper.properties sleep 3 kafka-server-start -daemon $KAFKA_HOME/config/server.properties echo "Kafka and ZK started" } stop() { kafka-server-stop zookeeper-server-stop echo "Kafka and ZK stopped" } case "$1" in start) start ;; stop) stop ;; *) echo "Usage: $0 {start|stop}" ;; esac

注意sleep 3那行——给 ZooKeeper 一点启动时间再起 Kafka,能避免"Kafka 连不上 ZK"的偶发失败。这个等待时间根据你机器性能可以调整,慢的机器可能要 5 秒。

6.3 那些文档里不会写的坑

最后分享几个我踩过的、常规教程基本不提的坑。

第一个,brew 升级 Kafka 之后,配置文件的路径和内容可能变。你之前写好的启动脚本可能突然失效,因为 cellar 目录下的版本号变了。所以脚本里千万别写死版本号,用brew --prefix kafka这种动态方式。

第二个,控制台消费者如果不指定 group,每次启动都是新组,会从头消费(配合 --from-beginning)或者只读新的。如果你发现消费行为不符合预期,先检查 group 是不是每次都变了。

第三个,Mac 休眠再唤醒后,Kafka 有时会假死。进程还在,端口还占着,但就是连不上。这种情况我一般直接重启 Kafka,别浪费时间排查,本地环境不值得。

第四个,topic 名字里不要用点号(.)和斜杠(/)。Kafka 内部会把点号转成下划线用于指标命名,斜杠在某些版本会引发路径问题。用中划线(-)最安全。

这套流程我在几台不同芯片的 Mac 上跑过,Intel 和 Apple Silicon 都验证过,路径差异是唯一的变量,其他逻辑完全一致。你把brew --prefix kafka这一步做对,剩下的就是复制粘贴的事。真正需要你理解的,是 ZooKeeper 和 KRaft 两种模式的区别,以及分区、副本、key 这些概念在实操中怎么体现——这些搞明白了,换任何机器、任何版本,你都能自己把环境搭起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询