用了好几年 RabbitMQ,一直都是在云主机上玩单机,直到有次需要搭一套高可用的集群给项目组做演示,手头又没有多余的机器,我盯着柜子里三台吃灰的旧 Android 手机发了会儿呆,突然冒出个念头:Radio 上装个 Linux Deploy,把 RabbitMQ 集群搬到手机上去。这套方案折腾下来,踩坑无数,但也确实把“三台手机加一台 Linux”的 RabbitMQ 集群跑通了,所以把整个搭建过程、踩坑记录和最终配置都整理成文,给想低成本玩集群的读者留一份可抄的作业。
这个方案适合谁?想学 RabbitMQ 集群原理但又不愿意花几百块开云主机的学生、开发者,以及手头刚好有闲置 Android 手机、想物尽其用的折腾型玩家。整体思路非常简单:三台旧手机通过 Linux Deploy 运行 Debian,再配合一台普通 Linux 真机,四台设备组成一个 RabbitMQ 对等集群。手机负责“量大管饱”地当实验节点,Linux 真机负责跑一些需要稳定性的辅助服务,比如管理端和监控端。
1. 旧手机当 RabbitMQ 节点的可行性分析
1.1 RabbitMQ 集群到底解决了什么问题
RabbitMQ 是 Erlang 写的,生来就有分布式基因,单个节点和集群本质上是同一套代码在不同设备上的运行方式。单节点时,所有队列元数据、消息数据都存在这一台机器里,一旦这台机器挂了,消费者就彻底断了,生产者的消息也可能堆积丢失。集群解决的问题就是把这个单点风险分摊到多台机器上,节点之间共享用户、权限、队列、交换机等元数据,消息则由某个节点的队列主副本承担,通过镜像队列或仲裁队列把数据复制到其他节点,保证单个节点宕机时业务不中断。
我在这次实验里用到的产品能力其实很基础:跨节点元数据同步、队列高可用策略、客户端重连后自动切换节点。这些恰恰是我在书上看了一百遍但一直没机会动手验证的东西,单机上根本触发不了交换机同步和脑裂恢复这些场景,只有真正拉起多个节点才能理解所谓“集群”和“多开几个进程”之间的本质区别。
1.2 三机一机的“草台班子”架构
本次最终架构是四节点集群:
| 设备 | 名称 | 作用 | 说明 |
|---|---|---|---|
| 手机A | node1 | RabbitMQ 节点 | Linux Deploy 跑 Debian |
| 手机B | node2 | RabbitMQ 节点 | Linux Deploy 跑 Debian |
| 手机C | node3 | RabbitMQ 节点 | Linux Deploy 跑 Debian |
| Linux 真机 | node4 | RabbitMQ 节点 + 管理端 | x86 架构,稳定电源 |
四台设备全部接在同一个家用路由器上,组成一个局域网集群。三台手机因为 WiFi 信道不稳定,实际吞吐只能满足开发测试级别的使用;Linux 真机因为网线直连,作为集群里最稳定的一个节点,同时也是管理后台的主要入口。
这个配置看起来有点“非主流”,但作为学习环境反而很合适。四节点能真实展示 RabbitMQ 集群里主节点切换、队列副本同步、节点脑裂分区等行为,而三台手机由于硬件水平参差不齐,还能顺便测试 RabbitMQ 在低配环境下的资源占用边界,这一点是普通服务器集群完全测不出来的。
1.3 手机硬件的客观评估
开始之前先把丑话说在前面。旧手机的 CPU 虽然是 ARM 架构,但处理消息路由和队列转发绰绰有余;真正拖后腿的是内存、磁盘和网络稳定度。我手头三台手机都是 3GB 到 4GB 内存,跑起来之后每台占用 500MB 左右,尚可接受,但系统本身、安卓框架和后台服务还要吃不少内存,实际上留给 RabbitMQ 的余量并不宽裕。内置存储用的是 eMMC 闪存,连续写入日志压力大时会发热掉速,所以后面必须限制日志级别和磁盘写入频率。
网络是最不稳定的环节。手机的 WiFi 在锁屏或休眠时会自动断开,即使强制保持常连,信号抖动也会导致集群节点之间偶发断连,非常考验 RabbitMQ 的分区处理机制。这反而是整个实验最有价值的部分:你几乎等于是在一个半失真的网络环境下压测集群韧性,比在云主机上模拟网络切割要真实得多。
2. Linux Deploy 建环境:这一步最容易卡壳
2.1 root、镜像大小和发行版选择
要在手机上跑 Linux Deploy,前提是手机已经获得 root 权限。各家机型的 root 方法不一样,通用思路是通过解锁引导加载程序后刷入第三方内核和 root 管理工具。这一步细节多,但网上针对机型的教程都很成熟,建议照着对应型号的步骤慢慢来,不要图快。
装好 root 环境后,再从应用商店安装 Linux Deploy。它的工作原理是在 Android 的文件系统里创建一个 Linux 发行版的根文件系统镜像,然后通过 chroot 方式让这个镜像里的程序直接运行在 Android 内核之上,不需要虚拟机,也不影响原有 Android 系统。界面上的关键设置如下:
- 发行版:选择 Debian。Ubuntu 也能跑,但 Debian 的软件源更稳定,包依赖关系改动的频率低,不容易把环境弄坏。
- 架构:选择 arm64。现代 Android 手机基本都是 aarch64 架构,别选成 armhf,否则 apt 源里的二进制包可能会不匹配。
- 镜像大小:我设的 8GB。4GB 也可能够用,但 RabbitMQ 跑久了会产生日志和持久化数据,空间留大一点免得中途清理。
- 文件系统:ext4,目标路径设在 /data 分区下,利用空白存储。别放在 /sdcard 上,FAT/exFAT 不支持 chroot 需要的文件权限。
- 用户名和密码:自定义一个普通用户用于 SSH 登录,root 密码单独设置,之后全部通过 SSH 操作。
设置完成后点“安装”,它会自动下载发行版基础包并生成镜像文件。这一步耗时较长,和网速有关,我当时等了差不多二十分钟。
2.2 chroot 环境的服务启动方式差异
Linux Deploy 创建的是一个 chroot 环境,不是完整的虚拟机,Systemd 作为 init 进程在多数情况下没办法直接启动,因为 PID 1 还握在 Android 手里。这就导致了一个比较容易困惑的问题:你顺手敲systemctl start rabbitmq-server可能会报错,或者明明提示启动成功了,但端口就是没监听。
解决办法是绕开 systemd,直接用以下三种方式之一:Linux Deploy 设置里自带“初始化”选项,可以在挂载 chroot 时执行 RC 脚本;也可以打开 SSH 服务,进到容器里用service命令管理 SysV 脚本;最稳妥的是直接用rabbitmq-server -detached以守护进程方式启动 RabbitMQ,不依赖任何 init 系统。
我在这次搭建里全部采用了rabbitmq-server -detached手动启动的方式,配合 Linux Deploy 的开机自动挂载和启动脚本,基本能实现手机重启后自动拉起服务。关于这个,后面长期运维那一章会专门讲。
2.3 网络与主机名规划:成败的决定性因素
RabbitMQ 集群节点之间通过 Erlang 分布式协议互相通信,节点名的解析依赖于主机名和 IP 的映射关系。如果各节点的主机名对不上,或者 /etc/hosts 里的 IP 是旧的,集群根本握不上手。这一步必须在安装 RabbitMQ 之前就规划好。
我这边的规划是:node1、node2、node3 对应三台手机,node4 对应 Linux 真机。在每台设备的 /etc/hostname 里写清楚自己的名字,然后在路由器的 DHCP 里给每台设备分配固定 IP,再在每台设备的 /etc/hosts 里统一写上四行静态解析。以 node1 为例:
192.168.1.10 node1 192.168.1.11 node2 192.168.1.12 node3 192.168.1.13 node4有个非常容易被忽略的坑:手机 WiFi 默认启用了 MAC 地址随机化,每次重新连接 WiFi 都会换一个 MAC,导致路由器动态分配的固定 IP 失效。需要在手机的 WiFi 设置里关闭“随机 MAC”功能,改为使用设备真实 MAC,然后路由器才能稳定绑定 IP。这个坑我踩了整整一个晚上,一直以为是 RabbitMQ 配置有误,结果是手机地址变了。
四台设备的 /etc/hosts 配好以后,先用ping node2、ping node3这种命令验证一下跨节点名称解析是否正常,再进入下一步。名称解析不通,后面的集群搭建寸步难行。
3. 单节点内跑起 RabbitMQ:版本、配置和启动
3.1 Erlang 和 RabbitMQ 版本配对注意事项
RabbitMQ 是 Erlang 写的,对 Erlang 的版本有严格的兼容范围。Debian 软件源里自带的 RabbitMQ 和 Erlang 通常是经过打包维护的,版本匹配性比较好,所以我建议直接装发行版源里的组合,而不是去官网下载最新版强行装上,否则很容易出现 RabbitMQ 连不上 Erlang 节点的问题。
在三台手机和 Linux 真机上执行相同的安装命令:
apt update apt install -y erlang-nox rabbitmq-servererlang-nox是不带图形界面的 Erlang 运行时,正好符合服务器的需求。安装完成后先别急着启动,因为四台机器的默认主机名和节点名都需要确认一遍,然后用下面这两节的内容把配置写好再统一启动。
3.2 三份关键配置:内存高水位、节点名和监听端口
RabbitMQ 默认会根据系统总内存设定内存高水位,超过阈值就触发流控,阻止生产者继续发送消息。在服务器上这没问题,但在手机这个环境下默认值太激进了,因为手机的总内存被 Android 占用一大半,chroot 里的 RabbitMQ 看到的内存并不可靠。
我在每台机器的/etc/rabbitmq/rabbitmq-env.conf里指定节点名:
NODENAME=rabbit@node1 NODE_PORT=5672在/etc/rabbitmq/rabbitmq.conf里限制内存和磁盘:
listeners.tcp.default = 5672 vm_memory_high_watermark.absolute = 512MiB vm_memory_high_watermark_paging_ratio = 0.5 disk_free_limit.relative = 1.0这里我把内存高水位强制限制为 512MB,这样即使手机总内存只有 3GB,RabbitMQ 也不会因为误判系统内存而把整个手机卡死。paging_ratio设成 0.5 表示在达到 256MB 时就开始把内存消息换页到磁盘,尽量压缩内存峰值。
磁盘空间方面,chroot 镜像文件只有 8GB,日志和队列持久化都在 /var/lib/rabbitmq 下,所以我额外设置了log.file.level = warning,把日志级别从默认的 info 降下来,省得很说一句。这个配置对长时间运行特别重要,否则日志就能把一个 8GB 镜像吃满。
3.3 启动、检查与管理插件开通
启动命令不依赖 systemd,我统一用:
rabbitmq-server -detached然后等待服务就绪:
rabbitmqctl await_startup rabbitmqctl statusstatus能看到 Erlang 节点名、内存使用、磁盘可用空间和已加载的插件列表。确认单节点正常后,先启用管理插件:
rabbitmq-plugins enable rabbitmq_managementguest 用户默认只能从本机访问,为了能从电脑浏览器登录管理界面,创建一个管理员账号:
rabbitmqctl add_user admin your_password rabbitmqctl set_user_tags admin administrator rabbitmqctl set_permissions admin ".*" ".*" ".*"到这里,四个节点上的 RabbitMQ 还是互相独立的单机状态。四台的安装步骤完全一样,唯一区别是NODENAME里的主机名不同。建议先把每台node单独启动并确认rabbitmqctl status正常,再进入集群搭建阶段,这样出问题时容易隔离。
4. 集群握手过程:让节点互相认亲的每一个关键点
4.1 EPMD 和端口 4369:先把通信底层打通
RabbitMQ 集群节点之间的连接建立在 Erlang 分布式节点之上,所有节点启动时都会启动一个叫 EPMD 的守护进程,负责记录本机 Erlang 节点名和端口的对应关系。其他节点想连接某台机器时,会先访问它的 4369 端口询问目标服务监听的真实端口,然后建立数据连接。
因此 4369 是集群通信的门面,必须对外开放且能被其他节点访问。手机 Linux Deploy 环境虽然只是 chroot,但网络命名空间和 Android 是共享的,所以防火墙规则也要检查。测试方法很简单:
telnet node1 4369如果连接失败,先看 RabbitMQ 是否真的在跑,再看 hosts 解析是否指向了错误 IP,最后检查有没有第三方防火墙软件拦截了入站连接。我遇到过一种特殊情况是路由器开了 AP 隔离,导致 WiFi 设备之间互相不能访问,这个问题只能在路由器管理页里关掉“无线隔离”选项。
4.2 Cookie 同步:最常见的认证失败根源
Erlang 节点之间的认证机制很简单:所有节点要持有一份完全相同的.erlang.cookie文件,相当于集群共享密钥。RabbitMQ 安装时会在/var/lib/rabbitmq/.erlang.cookie生成一串随机字符串,默认情况下每个节点各不相同,如果不做同步,节点间连接时会直接报authentication failure。
我们需要做的就是把 node1 上的 cookie 内容复制到另外三台设备。先查看 node1 的 cookie:
cat /var/lib/rabbitmq/.erlang.cookie然后把这段字符串写到其他三台设备的同一个文件里,并且设置严格权限,RabbitMQ 对 cookie 权限非常敏感,权限太宽松会直接拒绝认证:
chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie chmod 400 /var/lib/rabbitmq/.erlang.cookiecookie 同步完以后,重启所有节点的 RabbitMQ 服务让新 cookie 生效。这一步做完,节点之间才有“互信”的基础,否则后面join_cluster基本一步一个错。
4.3 加入集群命令与磁盘/内存角色选择
以 node2 加入 node1 为例,在 node2 上执行以下命令:
rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl join_cluster rabbit@node1 rabbitmqctl start_app逐条解释一下:stop_app让当前节点的 RabbitMQ 应用停止,但 Erlang 虚拟机还在运行;reset清空当前节点本地已有的数据库和元数据,这是把节点“清零”的过程;join_cluster rabbit@node1让当前节点以 node1 作为联系点加入集群;start_app启动当前节点应用,加入完成后自动向集群广播自己的身份。
node3 和 node4 的操作完全一样,只是把rabbit@node1改成rabbit@node1,也就是始终以第一个节点作为联系点。千万不要让 node3 去 join node2,如果在 node2 还没完全稳定时就加入,容易出现元数据冲突,后面还得 reset 再来一遍。
关于磁盘节点和内存节点的选择:默认情况下所有节点都是磁盘节点,也就是元数据必须落盘。RabbitMQ 的官方文档并不推荐过多使用内存节点,而且新版里内存节点的支持越来越边缘化,所以我这次没有做任何特殊配置,四个节点全部保持磁盘节点。这样虽然磁盘占用多一些,但稳定性高,尤其适合手机这种断电风险大的设备。
4.4 验证 cluster_status 与运行时消息队列分布
所有节点加入完成后,随便挑一台执行:
rabbitmqctl cluster_status正常输出里会出现几个关键信息:Running Nodes 列出当前在线节点,Disk Nodes 列出磁盘节点,另外还有 Partitions 分区列表,刚开始应该为空。我当时的输出里同时出现 node1、node2、node3、node4 四个运行节点时,悬着的心才落下来。
此时集群的元数据已经一体的,但消息数据还没有自动复制。你在任何节点上声明一个队列,这个队列的主副本只会存在于声明它的那台节点。如果这台节点宕机,队列就不可用,除非提前配置镜像队列或仲裁队列。所以集群搭完,距离“高可用”还差一步。
5. 把 Linux 真机拉进来并验证故障转移
5.1 Linux 节点与手机节点的差异处理
node4 是 x86 架构的普通 Linux 机器,安装步骤和三台手机完全一致,唯一要注意的是 Erlang 包架构不同,apt 会自动选择 amd64 版本,不需要额外操心。它的最大优势是网络稳定且内存更大,所以我把它作为管理后台的主入口,平时访问管理界面都通过 node4 的 IP 进行。
加入集群的命令仍然是先stop_app、reset,然后join_cluster rabbit@node1,最后start_app。到这里四台机器全部处于 Running Nodes 列表里,一个混合 ARM 和 x86 的 RabbitMQ 集群正式成形。
5.2 用镜像队列与仲裁队列把消息副本铺开
普通队列只有主副本,不是真的高可用。RabbitMQ 提供两种方式提升可用性:经典镜像队列和仲裁队列。镜像队列通过 Policy 策略把所有副本同步到符合条件的节点,老牌方案,配置简单;仲裁队列是 RabbitMQ 3.8 以后主推的高可用队列,基于 Raft 共识算法实现,更偏向于数据一致性。
我这次在两个场景里都做了验证。先用策略批量创建镜像队列:
rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all","ha-sync-mode":"automatic"}'然后再创建一个仲裁队列策略:
rabbitmqctl set_policy qq "^qq\." '{"queue-type":"quorum"}' --apply-to queues这里的^ha\.和^qq\.是队列名称的前缀匹配规则。之后我声明了名称以 ha. 开头和 qq. 开头的队列,它们就会自动套用对应策略。需要注意,仲裁队列在手机这种 eMMC 存储上写放大比较明显,因为 Raft 日志每个消息都要追加写盘,所以我在三台手机上更多使用镜像队列做测试,仲裁队列主要放在强制数据一致性的验证场景中。
5.3 故障注入测试:拔掉一台手机看看会发生什么
集群搭好之后,我做了个简单粗暴的测试:直接在 node2 手机上执行rabbitmqctl stop,彻底关掉 RabbitMQ 应用。然后从 node4 上执行:
rabbitmqctl cluster_status此时 Running Nodes 列表里应该只剩 node1、node3、node4,node2 消失。接着我用一个客户端连接到集群里的 node4,连续往qq.test队列发送消息并消费,确认消息链路没断。由于仲裁队列在四节点集群里的多数派是 3 个节点,node2 挂掉后其余 3 个仍然满足多数派条件,队列继续正常工作。
随后我重新启动 node2,直接执行rabbitmq-server -detached,再查cluster_status,发现 node2 自动重新回到 Running Nodes 列表,不需要重新 join,这说明集群的节点发现机制和自动重新连接机制工作正常。
这个实验最有价值的结论是:节点重启后能够自动归群,靠的是 hosts 域名解析和 cookie 互信。只要这两点不被破坏,集群容错能力就有基础保证。
6. 长期跑下来的稳定性心得与避坑操作
6.1 手机睡眠和 WiFi 掉线是最大的敌人
手机不像服务器,屏幕一关,系统就进入深度休眠,CPU 频率被压到最低,网络连接也可能被 Android 挂起。我在测试时发现,锁屏一段时间后,node1 和其他节点之间会出现周期性断连,轻则网络抖动,重则直接触发集群分区。
解决的组合拳包括三招:第一,插着充电器,并开启 Android 开发者选项里的“保持唤醒”功能,让系统在充电状态下不进入深度休眠;第二,把 WiFi 高级设置改为“休眠状态下保持连接”,关闭省电模式对网络的影响;第三,在 Linux Deploy 的设置里打开开机自动挂载和启动脚本,让手机重启后不需要人工干预就能拉起整个服务链。
即便如此,手机 WiFi 在长时间高强度读写下还是可能出现信号不稳定。所以如果你拿这套环境做长跑测试,建议把流量控制在每秒几十条消息以内,别把手机节点当生产环境压测机用。
6.2 IP 变化了怎么办:保留地址、静态 IP 和 hosts 联动
手机和普通服务器的最大区别是它靠 DHCP 获取地址,DHCP 租约到期后可能换一个 IP。集群里的 /etc/hosts 全是静态写的,如果有一台 IP 变了,其他三台就连不上它了。
我的解决办法是双保险:先把手机的“随机 MAC”关掉,在路由器里对四台设备做 MAC 绑定,固定 DHCP 分配的 IP;然后再进到每台手机 Linux Deploy 环境里设置静态 IP,直接把 IP 写在/etc/network/interfaces里,不再依赖 DHCP。静态 IP 的写法很简单,以 node1 为例:
auto wlan0 iface wlan0 inet static address 192.168.1.10 netmask 255.255.255.0 gateway 192.168.1.1 dns-nameservers 192.168.1.1注意手机 chroot 里的 wlan0 网卡是 Android 创建的虚拟接口,名字可能因手机型号不同而不同,常见的是 wlan0 或 eth0,先跑ip addr看一眼再改。这一步配置成功后,即使 WiFi 重连,IP 也不会变,集群的 hosts 就不需要频繁修改。
6.3 内存、日志和磁盘镜像:手机存储经不起日志轰炸
手机 eMMC 的寿命和写入速度都不如服务器 SSD,RabbitMQ 默认的 info 级别日志每秒钟都可能刷出好几条,时间一长就能把 8GB 的 chroot 镜像填满。这里我强烈建议把日志级别降到 warning,并且设置日志轮转:
log.file.level = warning log.file.rotation.size = 10485760 log.file.rotation.count = 5同时把 RabbitMQ 的持久化目录尽量精简,定期清理/var/log/rabbitmq下的旧日志。另外,仲裁队列的 Raft 日志会持续追加写盘,如果你想长时间保留队列消息,务必监控磁盘剩余空间,避免镜像写满后 RabbitMQ 自动进入磁盘阻塞模式,那会让整个集群拒绝写入。
6.4 分区处理策略:autoheal 在手机环境更省心
手机网络抖动太常见,节点之间的心跳一旦超时就会触发分区检测,RabbitMQ 会把集群切成几个独立分区,每个分区都认为自己才是真正的集群。如果不配置处理策略,分区恢复后数据会冲突,而且行为非常诡异。
RabbitMQ 配置文件里可以设两类策略:pause_minority和autoheal。前者在分区发生时会自动暂停“少数派”节点,优点是避免数据分歧,缺点是一旦网络抖动,少数派节点会频繁进入暂停状态;后者会在网络恢复后自动重启节点并尝试合并数据,对手机这种不稳定设备更友好。我把四台节点全都配置成:
cluster_partition_handling = autoheal另外提醒一点,仲裁队列在分区状态下,会严格按照多数派规则运行,四节点集群的多数派是 3 个节点。如果有两台手机同时掉线,仲裁队列会停止服务,这不是配置问题,而是 Raft 多数派的固有约束。想更稳的话,建议要么保持三个手机节点,要么再加一台 Linux 真机,让节点总数保持奇数。
6.5 这套“手机集群”的最终定位
把三台旧手机和一台 Linux 真机组成 RabbitMQ 集群这件事,实用性上其实不输一台入门级云服务器,但也仅仅是“开发测试级”的实用性。手机的优势是零成本、低功耗、能完整复现分布式系统的各种行为;劣势是网络稳定性、写入性能和长期可靠性都存在明显瓶颈。
如果只是学习 RabbitMQ 的高可用机制、测试镜像队列和仲裁队列的差异、验证集群脑裂恢复流程,这套环境性价比极高。但如果要跑到生产环境承载真实业务流量,我还是建议至少把节点换成三台云主机。手机集群可以当实验床,不适合当主营业务承载平台。
最后再分享一个小技巧:我在每台手机上贴了标签,写好 node1/node2/node3 的 IP 和密码,然后每次改动配置都顺手用scp把/etc/rabbitmq/rabbitmq.conf从旧配置同步到其他节点,避免四台机器配置漂移。手机这个东西一旦多了,管理上的麻烦往往比技术上的麻烦更难缠。