news 2026/10/19 10:15:48

树莓派搭建Kafka与RabbitMQ集群实战:低预算高可用消息队列

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
树莓派搭建Kafka与RabbitMQ集群实战:低预算高可用消息队列

三台树莓派组一套消息队列集群,同时跑Kafka和RabbitMQ,听起来像用玩具车拉钢筋,但真试过之后,我发现这事比预想靠谱得多。它能做什么?一套不烧钱、随开随关、能反复制造故障来练手的中间件实验环境;一个在边缘场景下做本地异步消息流转的省电方案;也是理解Kafka的副本选举、RabbitMQ的队列复制这些原理的最佳实物教具。如果你是做后台开发、运维,或者正在学消息队列,想用很少的预算搭出接近真实的集群环境,这篇文章就是按我的实际操作过程写的,从选硬件、装系统、部署组件到压测和排障,一条线走完。

1. 图什么:树莓派跑中间件的动机与边界

1.1 这类需求只有树莓派能满足

很多人学Kafka和RabbitMQ,习惯在台式机或笔记本上用Docker起一个单节点容器,点几下把消息发出去,就觉得自己会了。但单节点和真正的集群差别非常大:Kafka的分区副本怎么分布、Controller挂了谁顶上、ISR怎么收缩;RabbitMQ的镜像队列怎么同步、仲裁队列靠什么机制选举——这些只有真正拉起三台机器、组一个多节点集群,才会有体感。

云上组三台机器当然也行,但一个月账单摆在那里,按这个费率跑一年,够再买好几套树莓派了。树莓派可以做到一次几百块投入,电费几乎忽略不计,还能干一个云主机不敢干的事情:杀进程、拔网线、直接断电。这些破坏性操作恰恰是验证消息队列高可用机制最有效的手段。我搭这套集群的出发点,就是需要一台能折腾、折腾坏了不心疼的实验环境。

1.2 想清楚边界再动手

不过也得泼一盆冷水:树莓派上的消息队列集群,不是生产级替代品。ARM架构的CPU性能、4GB左右的内存、USB外接固态的IO能力,和一台正经的x86服务器差了一个量级。它能承载的,是学习、预研、边缘小流量处理、开发联调环境这类场景。

标题里说的“高性能”,指的是一个相对概念:把副本数、批量参数、确认机制、持久化这些生产配置全部拉满,三块ARM小板上还能跑出多少真实吞吐。这个数字比我动手之前预想的高不少,后面压测数据部分会详细展示。动手之前先明确这个预期,就不会在测试时觉得“怎么这么慢”,也不会产生不切实际的幻想。

2. 硬件选型与系统底子:少一个环节都不行

2.1 型号、内存与散热

我的集群是三台树莓派混搭。树莓派4B 4GB是底线,预算够建议直接上树莓派5的8GB版本,处理压测时CPU余量更大。不过混搭也完全没问题:Kafka和RabbitMQ的节点角色并不绑定机型,只要三台都在同一个千兆交换机下,性能差异由负载均衡自然分摊。

这里必须强调散热。树莓派全速压测时,CPU温度上升很快,一旦温度冲到85度左右就会强制降频,压测成绩会突然跳水。我一开始没装风扇,Kafka的producer测试跑到一半,吞吐从4万条每秒掉到不足2万,日志里全是CPU降频记录。后来加装了铝制外壳加风扇,温度稳定在60度左右,测试曲线才平稳下来。所以散热不是可选项,是必选项。

2.2 存储介质:SD卡是最大的坑

第一周我图省事,三台树莓派都插着高档SD卡跑Kafka。结果不到半个月,有两张卡就出现了坏块,Kafka日志里开始频繁报IO错误。原因不复杂:Kafka的设计高度依赖底层存储的顺序写入能力,消息落盘是持续不断的操作,而SD卡恰恰是为了顺序读设计的,对持续随机写入的耐受度很差,寿命会迅速消耗。

后来我把存储全部换成了USB外接SSD。接口优先USB 3.0,用带电源的硬盘盒子或NVMe转USB盒子都行。文件系统用ext4,数据分区单独挂载,避免和系统日志混在一起。把Kafka的log.dirs和RabbitMQ的mnesia目录都放到SSD上之后,IO方面的坑基本消失了。有一点很关键:如果系统在SD卡上而数据在SSD上,虽然也能跑,但系统层的日志和swap还是会磨损SD卡,所以我的做法是整个系统都装在SSD上,SD卡直接退休。

2.3 网络与系统裁剪

所有树莓派必须走有线网络,用网线直连同一台千兆交换机。Wi-Fi不要考虑,消息队列是延迟敏感应用,Wi-Fi的抖动会立刻体现在生产消费延迟上,还会触发一堆连接重试。

系统这边用64位的Raspberry Pi OS Lite版本,不带桌面环境,装完进入字符界面。接下来做三件事:固定主机名node1、node2、node3;把文件句柄数调高,Kafka和Erlang在消息量大时都会打开大量fd;装好时间同步服务。时间同步经常被忽略,但Java系组件对时钟跳跃很敏感,如果三台机器时间不一致,Kafka的Raft协议会产生各种奇怪的元数据异常。

3. Kafka集群部署:KRaft模式的实际配置与启动过程

3.1 为什么跳过ZooKeeper直接用KRaft

Kafka以前的架构依赖ZooKeeper做元数据管理,但ZooKeeper本身也是个Java进程,在4GB内存的机器上,每个节点多跑一个ZK就意味着多300到500MB的内存开销,三台机器就是近1GB被白白吃掉。KRaft模式是Kafka原生的Raft协议实现,把元数据管理直接内建在broker里,省掉了独立的ZooKeeper集群。

在树莓派这种资源受限的场景下,KRaft的价值特别明显。我用的是Kafka 3.7.1版本,三个节点都同时承担controller和broker两种角色,三份元数据通过Raft协议保持一致。注意一个细节:如果是大幅生产环境,官方会建议把controller角色单独拆出来,但树莓派实验集群没有这个必要,controller压力不大,混合角色反而是最优配置。

3.2 三个节点的配置差异点

以node1的配置为例,完整写在/opt/kafka/config/kraft/server.properties中:

process.roles=controller,broker node.id=1 controller.quorum.voters=1@192.168.1.11:9093,2@192.168.1.12:9093,3@192.168.1.13:9093 listeners=PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 advertised.listeners=PLAINTEXT://192.168.1.11:9092 controller.listener.names=CONTROLLER listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT inter.broker.listener.name=PLAINTEXT log.dirs=/data/kafka/kraft-logs 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 auto.create.topics.enable=false log.retention.hours=24 log.segment.bytes=104857600

node2和node3只需要把node.id改成2、3,然后修改advertised.listeners为自己的IP。其他内容保持一致。

有两个地方值得单独说。第一,advertised.listeners非常容易漏改,它的作用是告诉客户端和其他broker“你可以通过这个地址访问我”,如果这里写的还是node1的IP,其他节点连上来时就会指向错误地址,表现为客户端能连上却收发不到数据。第二,inter.broker.listener.name必须明确指定,否则节点间的数据复制会走错的监听器,压测时会奇怪吞吐为什么那么低。

3.3 初始化与启动顺序

KRaft模式初始化比ZooKeeper模式简单,核心是让三个节点使用同一个集群ID格式化存储目录。先在node1上生成集群ID:

bin/kafka-storage.sh random-uuid

把输出的UUID保存下来,然后在三个节点上都执行同一段命令,注意-t参数必须相同:

bin/kafka-storage.sh format -t <上一步获得的UUID> -c config/kraft/server.properties

这一步会在log.dirs下生成包含集群元数据的文件。集群ID不一致会导致节点无法组成quorum,这是常见的初始化错误。

启动时,我建议先把所有节点设置好JVM参数再启动。在启动脚本前通过环境变量覆盖堆内存:

export KAFKA_HEAP_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC" bin/kafka-server-start.sh -daemon config/kraft/server.properties

Kafka的特点是好钢用在操作系统页缓存上,堆内存不是越高越好,反而应该控制在合理范围,把Linux页缓存让给数据读写。4GB内存的树莓派给Kafka分512MB堆,再加上页缓存,大约1.8GB用于文件缓存,整个集群能跑得挺稳。默认的1GB堆反而会压缩页缓存空间,拉低吞吐。

启动后验证集群状态:

bin/kafka-metadata-quorum.sh --bootstrap-server 192.168.1.11:9092 status

看到三个节点都在线,且Leader信息正常,就说明Raft元数据集群已经建立。

3.4 创建主题与关键参数验证

测试集群用三副本主题验证数据分布:

bin/kafka-topics.sh --create --topic demo \ --partitions 3 --replication-factor 3 \ --bootstrap-server 192.168.1.11:9092 bin/kafka-topics.sh --describe \ --bootstrap-server 192.168.1.11:9092 --topic demo

describe结果应该显示三个分区的Leader和Replica分布在不同节点上,没有一个节点包揽所有分区。这个分布状态就是后续故障演练的基础:拔掉任何一台,另一台能立刻接任Leader。

有几个参数必须提前设置好,不然会在运行一段时间后出问题。offsets.topic.replication.factor必须等于3,这个主题保存消费者组提交位点,如果副本数不足,某个节点挂掉后消费者组协调就会混乱。min.insync.replicas设为2,配合生产端acks=all使用,可以保证每次写入至少落在两个同步副本上。auto.create.topics.enable我关掉了,避免客户端手滑创建一个单副本主题,单副本主题在实验环境里最容易造成“以为数据丢了”的误会。

4. RabbitMQ集群部署:Cookie、仲裁队列与内存水位

4.1 安装与Erlang Cookie

RabbitMQ 3.13版本,按官方推荐匹配Erlang版本。安装包的方式比源码编译省事得多,依赖关系由包管理器自动处理。装好之后第一件事,不是急着组集群,而是统一Erlang Cookie。

RabbitMQ集群节点之间通信依赖Erlang节点名和同一个cookie文件来互认身份。文件位置在/var/lib/rabbitmq/.erlang.cookie,内容必须三台完全一致。把node1上的文件内容复制到node2和node3,然后修正权限:

sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie sudo chmod 600 /var/lib/rabbitmq/.erlang.cookie

这里权限非常重要。权限过松会导致RabbitMQ启动时直接拒绝使用这个cookie,权限不足则集群连接报错。如果出现类似“Cookies must be equal in size”的信息,说明三台cookie内容不一致,别看别的了,这就是原因。

网络端口层面需要确认:4369是erlang的epmd端口,25672是节点间通信端口,5672是AMQP端口,15672是管理控制台。实验环境下直接全部允许内网访问,生产环境则要收敛到特定地址。

4.2 组建集群的标准操作

在node2和node3上依次执行四步命令,这是RabbitMQ加入集群的标准序列:

sudo rabbitmqctl stop_app sudo rabbitmqctl reset sudo rabbitmqctl join_cluster rabbit@node1 sudo rabbitmqctl start_app

注意几个容易理解错的点。stop_app是停掉RabbitMQ应用层,但Erlang节点本身还在运行,这是为了让reset能安全清除本机数据。reset会清空当前节点的数据库,所以绝不能在生产环境随意执行。join_cluster以node1作为联系人,把当前节点加入它所在的集群,指定rabbit@node1这个Erlang节点名,不是IP地址。

全部执行完,在任意节点上运行rabbitmqctl cluster_status,应该看到三个节点都在线,Disk nodes列出三台,Running nodes也列出三台。到这里RabbitMQ集群已经通了,但队列数据的复制策略还没配,这才是RabbitMQ集群真正的核心。

4.3 队列类型:仲裁队列还是镜像队列

很多人建完集群就开始生产消费,之后发现某个队列的消息一直只在某一台机器上,另外两台完全空闲,然后误以为集群没组好。其实经典队列在集群里默认只有一个节点持有数据,其他节点只存元数据,要实现消息数据的复制,必须靠额外策略。

现在的主流做法是仲裁队列。仲裁队列内部基于Raft复制消息,不需要额外的镜像策略,自动在集群多数节点之间同步数据,丢消息的概率比旧式镜像队列低很多。给以qq.开头的队列设置仲裁队列策略:

sudo rabbitmqctl set_policy -p / qq-policy "^qq\." \ '{"queue-type":"quorum","delivery-limit":5}' --apply-to queues

如果想继续使用经典镜像队列,比如要兼容比较老旧的客户端,可以单独开一套策略:

sudo rabbitmqctl set_policy -p / mirror-policy "^mirror\." \ '{"ha-mode":"all","ha-sync-mode":"automatic"}' --apply-to queues

两种队列类型可以用不同的前缀区分,比如qq.*走仲裁队列,mirror.*走镜像队列,避免混在一个队列上出现重复策略冲突。仲裁队列对网络分区友好,我建议新业务一律用仲裁队列,镜像队列留着做兼容。

4.4 内存和磁盘水位置配好

RabbitMQ在内存使用达到一定水位后会阻塞生产者,默认值是0.4,也就是4GB内存的机器用到1.6GB就停止收消息了。这个默认值对于实验环境偏保守。我在/etc/rabbitmq/rabbitmq.conf里做了调整:

vm_memory_high_watermark.relative = 0.5 disk_free_limit.absolute = 500MB

磁盘水位的设定也要提一下。默认方式是按可用空间的百分比计算,但树莓派的存储量级比较小,用绝对值更可控。磁盘剩余空间低于500MB时,RabbitMQ会自动进入只读保护模式,主动拒绝接收新消息。这个机制能防止磁盘被写满,但阈值如果配得太高,会在磁盘明明还有很多空间时莫名阻塞。

另外,RabbitMQ的数据目录在/var/lib/rabbitmq/mnesia,如果系统在SD卡上而这里没改,持续写入同样会让SD卡加速损坏。我在系统准备阶段就完成了全SD换SSD,这个目录自然就落在SSD上。

5. 压测数据:三块ARM小板的真实吞吐

5.1 测试工具与口径

Kafka用官方自带的kafka-producer-perf-test.sh和kafka-consumer-perf-test.sh,RabbitMQ用官方perf-test工具。测试口径尽量统一:每条消息1KB,持续跑60秒,记录稳定值。Kafka的producer开启acks=all,代表每次写入都等所有ISR副本确认,这是生产环境最常用的强一致配置。RabbitMQ这边使用仲裁队列并开启持久化,同样代表不丢数据的配置。所有节点接在同一台千兆交换机下,客户端也从同网段机器发起连接,避免把无线干扰算进结果里。

这份垃圾数据当然不能代表所有场景,但它能回答一个核心问题:在真实生产参数下,树莓派集群到底能稳定提供多少能力。

5.2 Kafka实测结果

测试场景实测吞吐
3副本、acks=all、生产者批量32KB、3个分区约4.1万条/秒,约40MB/秒
3副本、acks=1、生产者批量32KB、3个分区约7.2万条/秒
单消费者端消费约6.3万条/秒,约60MB/秒

4.1万条每秒的强一致写入,说实话比我预想的高。三台ARM四核跑到这个吞吐时,CPU占用率已经接近满,网络带宽反而只用了三分之一左右。这说明瓶颈在CPU处理路径和同步复制的网络开销,而不在千兆网口上。如果切换到acks=1,吞吐几乎翻倍,但代价是要接受极端情况下丢分区的风险,压测环境可以做对照,生产环境不建议这么开。

消费者端跑出60MB/s的读取速度,得益于Kafka的页缓存命中策略,操作系统把热数据都缓存在内存里,ARM CPU只是搬运工。这个数据说明树莓派集群在消费侧的表现比生产侧更好,适合做数据消费分析的边缘场景。

5.3 RabbitMQ实测结果

测试场景实测吞吐
仲裁队列、持久化、1生产者+1消费者约8000条/秒
经典镜像队列、全部节点复制、持久化约5600条/秒
非持久化经典队列约1.3万条/秒

RabbitMQ的数据明显比Kafka低一个数量级。这不奇怪,RabbitMQ本身就不是追求极限吞吐的中间件,它的价值在于灵活路由、多协议支持、TTL、死信这些消息管理能力。哪怕是最慢的镜像队列,5600条每秒的稳定速率放到真实业务里,也足够撑起内部工单流转、异步通知、任务调度这类场景,一天轻松几百万条消息。

5.4 这批数据的意义边界

这些数字不是用来跟x86服务器比的,x86上跑同样的测试,数字翻个三到五倍很正常。它的价值在于让团队在资源匮乏时能做出准确预算:假设一个边缘节点每天产生几百万条事件消息,峰谷差异很难预测,我们可以直接拍脑袋说树莓派集群能扛住这个流量。做实验环境、做教学演示、做小规模内部服务,这个上限绰绰有余;如果哪天发现流量涨了十倍,那时候再去买真正的服务器也不迟。

6. 三个月运行实录:断电、磁盘与网络闪断

6.1 断电之后机器起不来

树莓派最常见的故障就是断电。不管是家里跳闸还是手碰掉电源线,Kafka的KRaft日志和RabbitMQ的mnesia数据库都可能因为来不及正常落盘而出现损坏状态。

我遇到过最典型的一次:node3断电重启后,Kafka进程起不来,journalctl -u kafka显示日志目录存在非法记录。当时的处理顺序是先从另外两台正常节点确认quorum完整,再决定动作,而不是一上来就删数据目录。确认node1和node2的quorum正常后,我把node3的数据目录整个备份,重新format,再让node3作为新节点加入集群。虽然这台机器的历史数据没了,但三副本机制保证两个正常节点上的数据才是真正的权威副本,丢失不影响整体。

RabbitMQ的断电故障更常见。如果节点掉线后重连不成功,cluster_status里会看到分区状态,此时比直接重启更靠谱的是重置重入:

sudo rabbitmqctl stop_app sudo rabbitmqctl reset sudo rabbitmqctl join_cluster rabbit@node1 sudo rabbitmqctl start_app

这套命令在恢复异常节点时比service restart可靠得多。前提是队列已经配置了仲裁队列或镜像策略,reset清掉的只是本机陈旧数据,其余节点上的数据还能同步回来。

6.2 磁盘被积压消息塞满

有一次RabbitMQ某个队列的消费者端挂了,消息不断积压,几十GB的日志和消息数据把SSD直接塞满,然后所有生产者的channel都被磁盘水位报警阻塞,整个消息链路瘫痪。排查时先看哪个队列出了问题:

sudo rabbitmqctl list_queues name messages consumers

找到堆积队列后,如果确认消息不需要了,直接用purge_queue清空再恢复消费者。从那以后我加了两条防御措施:一是给关键队列设置TTL策略,让消息过期自动清除;二是写了个简单的cron脚本,每五分钟检查一次磁盘使用率,超过80%就发通知。Kafka侧则靠log.retention.hours和log.segment.bytes做硬性兜底,配合数据目录监控,不让日志无限膨胀。

6.3 网络闪断引发的分区

家用交换机偶尔抽风,我碰到过一次拔插网线之后,RabbitMQ三节点进入网络分区状态,cluster_status显示节点互相看不到,部分仲裁队列出现未确认状态。Kafka这边由Raft协议自动处理,基本无需干预,但RabbitMQ需要手动收敛。

处理思路是选一个数据最完整的节点作为基准,其他两个节点依次做stop_app、reset、join_cluster、start_app。注意这句话的意思是:reset会丢失该节点本地未同步的消息,如果当时队列没有配置复制策略,这部分消息就会永久消失。所以平时如果能选仲裁队列,就一定不要用默认经典队列裸跑,否则网络抖动一次,运气不好就真的丢数据了。

6.4 维护这套集群积累的几个习惯

跑了三个月,踩过的坑变成了一套固定习惯。所有配置文件都用文本管理,改动前先备份,出错时能秒回滚。升级任何组件前停服务,把全部数据目录打包成tar,树莓派的数据量级通常几十秒就能备份完,但回滚省下的时间可能是一整天。电源方面,给每个树莓派配独立电源,不要从电脑USB口取电,也不要用共享的劣质充电器。最后,关掉树莓派的蓝牙和Wi-Fi模块,只留有线网络,这些默认开启的无线模块除了制造延迟波动,没有任何用处。

如果哪天这套集群要迁移到更强的机器上,配置文件和数据目录是全部家当。想从树莓派升级到普通服务器,把Kafka的log.dirs和RabbitMQ的mnesia整体搬过去,节点重新注册,集群就活了。这就是小规模中间件集群一个非常现实的好处:它不娇贵,甚至可以被粗暴对待,但在这里学会的经验,换到更严肃的环境里依然完全适用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 10:15:19

沟通优先的CRM设计:让一线销售真正用起来的落地指南

1. DeskcommCRM这个名字&#xff0c;其实已经说清了产品方向第一次看到“DeskcommCRM”这个项目名时&#xff0c;我大概停了两秒。Desk、comm、CRM&#xff0c;这三个词拼在一起&#xff0c;产品定位其实已经写在了脸上&#xff1a;这是一个把“桌面办公”和“沟通协同”揉进客…

作者头像 李华
网站建设 2026/10/19 10:14:37

遥感影像与矢量点配准:获取像素坐标的完整指南

做遥感的人&#xff0c;十有八九都遇到过这个需求&#xff1a;手上有一批野外采集的矢量点&#xff0c;或者一批目标区域的地面控制点&#xff0c;想把这些点精确投到遥感影像上&#xff0c;看看每个点落在哪个像元、对应像元的光谱值是多少。这个需求的落点&#xff0c;就是标…

作者头像 李华
网站建设 2026/10/19 10:14:34

RDK X5部署YOLO全流程:从ONNX导出到BPU量化推理避坑指南

简介&#xff1a;面向新手小白的RDK X5部署YOLOv5源码教程包&#xff0c;覆盖从Windows下WSL2环境搭建、Ubuntu配置、YOLOv5源码获取&#xff0c;到Python虚拟环境创建、自定义数据集训练、ONNX模型转换、Docker镜像挂载以及模型量化压缩的完整部署流程。资源仅3个文件&#xf…

作者头像 李华
网站建设 2026/10/19 10:12:59

Flutter与原生通信全解析:通道机制、选型指南与实战排查

Flutter与原生通信这块&#xff0c;几乎每个做混合开发的人早晚都得碰。平时写Dart写得很开心&#xff0c;一遇到要调系统能力、接第三方原生SDK、或者跟已有的原生模块打通&#xff0c;通信这层就是绕不开的桥。我在实际项目里接过蓝牙设备、做过地图SDK封装、也搞过原生播放器…

作者头像 李华
网站建设 2026/10/19 10:12:59

NOMA与LDM:功率域非正交复用的机制、异同与工程实践

1. 非正交接入到底想打破什么&#xff1a;正交资源池的先天瓶颈 聊NOMA和LDM之前&#xff0c;先回到一个最基础的问题&#xff1a;为什么传统通信系统一直都在强调“正交”&#xff1f;从频分多址&#xff08;FDMA&#xff09;到时分多址&#xff08;TDMA&#xff09;&#xff…

作者头像 李华
网站建设 2026/10/19 10:10:21

一套Cadence视频教程工程,让Allegro PCB设计全流程跑通

简介&#xff1a;于争博士Cadence视频教程配套工程文件&#xff0c;面向电子设计自动化学习者与PCB设计入门和进阶人群&#xff0c;围绕Cadence Allegro与OrCAD工具链&#xff0c;提供从原理图设计、元器件库搭建到布局布线的完整实操案例&#xff0c;也适合希望巩固流程的电子…

作者头像 李华