上个月刚帮一台老掉牙的Windows Server上的Apache Apollo消息服务迁到了Linux虚拟机,前前后后折腾了三个晚上。这台Apollo是公司下单通知系统的消息中转站,每天有一批定时任务和实时消息往里塞,消息量不算大,但谁也不敢让它长时间停。说实话Apache Apollo本身已经停止维护好多年了,能在Windows上跑着已经算运气好,Windows服务器又时不时因为补丁、登录会话、内存占用闹脾气,老板终于松口:迁到Linux服务器上。
这篇文章不准备写那种“点几下鼠标就完成”的假教程,而是把真实的迁移命令、配置对照、踩坑记录都摊开来讲。不管你是还在维护老消息中间件,还是要把其他Java类服务从Windows搬到Linux,这套思路都能直接套用。核心就一句话:Apollo的配置和消息数据都在文件目录里,迁移的重点不是重装一个软件,而是把这套目录原封不动地搬过去,再把路径、权限、JDK、启动方式全部调整成Linux风格。
1. 迁移前必须搞明白的三件事
1.1 Apollo的“家底”到底放在哪里
很多人一开始会习惯性去找数据库迁移工具,其实Apollo根本不依赖外部的MySQL或者PostgreSQL,它的消息、队列、持久化订阅、虚拟主机配置都写在文件目录里。一个broker实例就是一个完整的目录,Windows上通常长这样:
text复制D:\apollo\bin\mybroker\
├── etc\ # 配置文件,包括broker.xml、users.properties、logback.xml
├── data\ # 消息数据,队列积压、游标、持久化订阅都在这里
├── log\ # 运行日志
├── tmp\ # 临时文件
└── bin\ # Windows下还会生成一些辅助脚本
这里最关键的是data目录,它是整个迁移的命根子。队列里积压了八千条消息、有一堆消费者订阅还没消费完,这些状态全部在data下面。配置文件丢了可以重新写,用户权限丢了可以补,唯独data丢了,业务方积压的消息就全没了,那才是事故。
所以我的第一条建议是:迁移前先搞清楚你的broker实例路径。别想当然认为Apollo装在D盘根目录就一切在D盘里,先到Windows服务管理器里看启动命令,或者看apollo-broker的进程路径,把真实路径找出来再说。
1.2 版本与JDK环境核对
Apache Apollo的版本号看着不复杂,但对JDK的要求比较死。我这次用的是1.7.1,官方要求JDK 8。Windows上原来装的是JDK 8,这个没问题,但如果你迁到Linux后图省事装了系统自带的OpenJDK 17,启动时十有八九会遇到UnsupportedClassVersionError,一查就是class文件版本不兼容。
建议迁移前先在Windows上执行一条命令把版本信息留个底:
bash复制apollo-broker --version
java -version
然后把Linux侧的JDK固定成JDK 8的某个OpenJDK版本。不要用JDK 11、不要用JDK 17,就算能强行跑起来,后面遇到奇怪的GC或者反射异常,排查成本远高于你提前装对版本的成本。
1.3 迁移范围与停机窗口
搞清楚数据在哪、版本匹配之后,第三个关键问题就是停机窗口。Apollo不像Kafka那样有原生的跨集群复制能力,想做到无缝切换很难,一般是停旧、搬数据、启动新、验证切换,整个流程要控制在业务允许的停机时间内。
我会先做一次预演:把Windows上的broker实例完整打包拷到测试Linux机器,启动后确认队列消息数、订阅关系、消息收发都正常,再约业务方正式切换。正式切换前还要通知所有消息生产方和消费方停写停读,等存量消息消费得差不多了再停broker,这样迁移过去的data目录会比较干净,积压消息也不容易丢。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从备份到启动:迁移全程实操
2.1 源端:Windows上的数据备份与干净停机
迁移的第一步不是打包,而是先让Apollo“安静下来”。我见过有人直接对着运行中的目录Ctrl+C复制,结果到Linux一启动,store文件不一致,broker直接拒绝启动。
正确操作分几步:
先记下当前运行状态,包括端口占用、连接数、队列积压,方便迁移后对比。然后通知业务方暂停写操作,观察队列消息是否被消费掉。等消息清得差不多了,用Windows下的broker脚本执行停止:
bash复制apollo-broker.cmd stop mybroker
如果这时候进程还没退干净,再看一眼任务管理器,确认java进程确实结束了。直接拔电源式强制下线是最容易把数据目录搞坏的。
停干净之后,把整个broker实例目录打成压缩包。Windows下没有好用的tar,我一般用一个特别稳妥的组合:
bash复制tar -czf apollo-broker-backup.tar.gz -C D:\apollo\bin mybroker --exclude=mybroker\log --exclude=mybroker\tmp
log和tmp这两个目录不是必须的,排除掉能省不少体积,打包速度也快。接着把这个包下载到一台能同时访问Windows和Linux的中转机,校验一下MD5或者SHA256,确认文件完整再传到Linux服务器上。这一步看起来啰嗦,但能避免后面因为网络传输丢包,导致你怀疑人生。
2.2 目的端:Linux环境初始化
Linux服务器这边的准备不要等拷贝的时候才做。我建议提前把下面这几件事做完:
新增一个专用系统用户,不要用root跑消息中间件,这是底线。我个人习惯用apollo这个用户名,家目录直接指向安装目录,后面做systemd服务化管理也方便:
bash复制useradd -r -m -d /opt/apollo -s /sbin/nologin apollo
mkdir -p /opt/apollo/instances
然后安装JDK 8。CentOS系用yum装,Debian系用apt装,装完以后确认JAVA_HOME已配置,并且切换到apollo用户也能读到:
bash复制export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk
export PATH=$JAVA_HOME/bin:$PATH
Apache Apollo的二进制发行包是跨平台的,Linux和Windows只是启动脚本不一样,核心jar包完全通用。我从官方仓库把对应1.7.1版本的tar.gz拉下来,解压到/opt/apollo:
bash复制tar -xzf apache-apollo-1.7.1-unix.tar.gz -C /opt/apollo
解压完目录名可能带版本号,我习惯改成不带版本的短名字,后面写systemd和脚本都省心:
bash复制mv /opt/apollo/apache-apollo-1.7.1 /opt/apollo/apollo-home
这时候不要急着动数据,先把Windows备份解压到目标路径:
bash复制tar -xzf apollo-broker-backup.tar.gz -C /opt/apollo/instances
解压出来的目录名仍然是mybroker,这样一个干净的Linux环境就准备好了。
2.3 数据与配置迁入
数据包解压进来之后,别急着启动,先做两件小事。
第一件,把所有文件权限归给apollo用户:
bash复制chown -R apollo:apollo /opt/apollo
chmod +x /opt/apollo/apollo-home/bin/*.sh
chmod +x /opt/apollo/apollo-home/bin/apollo-broker
第二件,检查配置文件里的路径。Windows上路径分隔符是反斜杠,Linux上是正斜杠。broker.xml、logback.xml里面凡是写了绝对路径的地方,都要挨个过一遍。比如logback.xml里常见的日志路径如果是D:\apollo\bin\mybroker\log,到了Linux就得改成/opt/apollo/instances/mybroker/log,否则Logback启动时会因为找不到目录直接报错。
我还会顺便检查一下broker.xml里的host和端口绑定。Windows上可能写的是localhost或者127.0.0.1,如果生产环境需要被其他服务器访问,到了Linux服务器上最好改成0.0.0.0,不然客户端连不上,控制台也打不开,排查半天发现是绑定地址太窄。
2.4 启动验证流程
第一次启动我不建议直接扔到后台,先用前台方式跑一次,方便盯着日志输出:
bash复制sudo -u apollo /opt/apollo/apollo-home/bin/apollo-broker run /opt/apollo/instances/mybroker
看到类似“Broker started”的日志,并且没有报错后,按Ctrl+C停掉,再改成后台启动。接着验证端口、管理台、队列数据三件事。
验证端口用ss命令,重点看消息端口、管理台端口是否在监听:
bash复制ss -lntp | grep java
验证管理台,直接用浏览器访问。Apollo的管理控制台默认一般在61680端口,登录后进入Virtual Host页面,查看队列列表和消息数,和迁移前Windows上截图对比一下。
验证消息收发,用一个测试生产者往某个临时队列写几条消息,再用消费者读出来,确认整个链路是通的。这一步千万别省,不要觉得登录控制台看着数据在就算成功,必须跑一遍真实的消息收发流程。
3. 核心配置与JVM参数:Windows风格到Linux风格的转换
3.1 broker.xml中的端口和协议
Apollo的broker.xml是整个实例配置的枢纽,里面同时包含了队列声明、协议监听器、管理控制台配置、虚拟主机权限规则。Windows和Linux这套配置基本是通用的,真正需要改的是监听地址和外部可访问性。
我的broker.xml核心部分长这样,供参考:
xml复制<broker xmlns="http://activemq.apache.org/schema/apollo">
<queue name="order.notify"/>
<queue name="order.timeout"/>
<acceptors>
<acceptor id="tcp" uri="tcp://0.0.0.0:61613"/>
<acceptor id="tls" uri="tls://0.0.0.0:61614"/>
</acceptors>
<jetty>
<webapp url="file:///opt/apollo/instances/mybroker/etc/webapps/store"/>
<connector id="web" host="0.0.0.0" port="61680"/>
</jetty>
</broker>
生产环境里不要照抄,重点是理解这几块。acceptor决定客户端用什么协议连,61613是STOMP默认端口,如果你用的是AMQP或者MQTT,端口会不一样。jetty块是管理控制台配置,host只监听127.0.0.1的话,外部浏览器永远打不开。迁移前把每个端口在Windows上确认一遍,迁移后在Linux上保持一致的对外暴露,能少很多联调问题。
3.2 users.properties权限与TLS证书路径
Apollo的用户权限配置在etc/users.properties里,一行就是一个用户,格式是“用户名=密码”,前面加叹号表示禁用。这个文件迁移后原样保留问题不大,但如果你遇到客户端认证失败,要检查文件编码、行尾符,以及文件权限。
Windows和Linux对文件权限的理解完全不一样。Windows不会因为你少一个chmod就不让你读,Linux会。我建议迁移后统一执行一次:
bash复制chmod 600 /opt/apollo/instances/mybroker/etc/users.properties
如果你原来在Windows上配置了TLS/SSL,还需要在broker.xml里找到keyStore绝对路径,Windows下常见的D:/apollo/certs/keystore.jks到了Linux必须改成新路径。JKS文件本身跨平台通用,不需要重新生成,但路径写错会让启动直接失败,而且日志提示往往是在TLS初始化阶段,不那么直观。
3.3 JVM参数迁移
这是最容易被忽视的部分。Windows上的Apollo启动脚本是.bat或者通过procrun注册成Windows服务,JVM参数写在批处理或者系统服务配置里。到了Linux,参数就要写进apollo-broker这个shell脚本或者systemd环境变量里。
我最先调整的是堆内存。原来的Windows机器内存32GB,Apollo的-Xmx设置成了24GB,新Linux虚拟机一共才16GB,照搬肯定OOM。根据队列积压量和并发连接数,我把-Xmx定成了8GB,-Xms跟着8GB,避免启动后频繁扩容触发性能抖动。
同样要清理的一个参数是-XX:MaxPermSize。Java 8以后PermGen已经被Metaspace替代,写了也不报错,但完全没有意义,容易误导后来维护的人。
给你一份简单对照,基本等于我的最终参数:
text复制JAVA_OPTS="-Xms8g -Xmx8g -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/apollo/logs"
G1GC在JDK 8下跑Apollo这种消息量不算特别大的场景很稳,默认的Parallel GC也问题不大,但G1的停顿控制更好,适合在线消息链路。
3.4 systemd服务化配置
Linux上最省心的运行方式不是nohup,是用systemd托管。好处是开机自启、崩溃自动拉起、日志统一管理。网上很多人用nohup加shell脚本跑,进程挂了没人知道,消息中间件这种关键组件不能这么随意。
我在/etc/systemd/system/apollo-broker.service里写了一份配置文件:
ini复制[Unit]
Description=Apache Apollo Broker
After=network.target
[Service]
Type=simple
User=apollo
Group=apollo
WorkingDirectory=/opt/apollo
Environment=JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk
ExecStart=/opt/apollo/apollo-home/bin/apollo-broker run /opt/apollo/instances/mybroker
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
写完后依次执行:
bash复制systemctl daemon-reload
systemctl enable apollo-broker.service
systemctl start apollo-broker.service
注意Type=simple意味着systemd认为进程一启动就算active,但Apollo的broker脚本内部可能有一段时间初始化,第一次执行start之后还是看一眼日志确认真正起来了,不要看到active就直接宣布成功。
4. 避坑手册:我踩过的那些坑和排查方法
这一节是全文最想让你保存的部分。迁移过程里我和同事遇到不少问题,有几类高频率出现,几乎是Windows迁Linux必踩,我把现象、原因、排查办法整理成一张速查表:
| 现象 | 常见原因 | 排查与解决 |
|---|---|---|
| 启动报UnsupportedClassVersionError | JDK版本太高,Apollo 1.7.x要求JDK 8 | 把JDK固定为8,清除其他版本干扰,用JAVA_HOME指定路径 |
| 端口重复绑定Address already in use | 之前的进程没退干净,或者systemd反复拉起老进程 | 用ss -lntp找占用进程,kill掉后重启,必要时ps检查所有java进程 |
| 日志乱码或配置文件解析失败 | Windows记事本把UTF-8文件改成了GBK或带BOM | 用file命令检查编码,统一转为UTF-8无BOM,vim里写set nobomb |
| 队列消息数变成0 | 只拷了配置目录,没有拷data目录 | 确认备份包里包含data,迁移后用控制台对比队列消息数 |
| 管理台打不开 | Jetty绑定127.0.0.1,或防火墙没放行端口 | 把broker.xml里web connector的host改成0.0.0.0,再确认安全组和firewall规则 |
| 客户端连不上消息端口 | 绑定地址是localhost,或者安全组只放行Windows端口 | 检查acceptor监听地址,用ss确认监听是否在0.0.0.0 |
| 日志目录Permission denied | 启动用户没有日志目录写权限 | 对broker实例目录执行chown -R apollo:apollo,不要用root启动 |
| 消息调度延迟、定时消息错乱 | Linux服务器系统时间漂移 | 配置chrony定时同步,第一步先手动date校正 |
上面这些坑里,我最想单独说的是编码和路径这两个问题,因为它们隐蔽性最强。Windows下用记事本改过xml,保存后可能变成带BOM的UTF-8,Linux下解析器对BOM的处理不完全一致,表面看文件正常,启动时却报解析错误。我自己吃过一次亏,后来学乖了,迁移后的所有配置都先执行一遍:
bash复制file etc/broker.xml
grep -r $'\r' etc/ --include="*.xml" --include="*.properties"
第二个命令是查CRLF换行符残留。虽然大部分情况下Java解析器能兼容CRLF,但在某些老版本Apollo里,properties文件带了回车符会把用户名密码搞坏,客户端认证一直失败,查了半天才发现是换行符的锅。
还有个大坑是关于系统时间的。Windows服务器如果一直跑着,时间即使漂了也是缓慢漂移,应用层感受不明显。Linux虚拟机如果宿主机超卖,时间漂移可能很夸张。Apollo这种消息中间件对时间敏感,尤其是定时消息和消息过期判定,时钟一跳,消费者端立刻出现消息“提前到期”或者“迟迟不出队”。迁移完第一件事就要确认时间同步:
bash复制timedatectl set-ntp true
chronyc tracking
如果公司内部有时间服务器,最好把NTP池改成内网源,既安全又稳定。这条建议不单是给Apollo迁移用,所有要迁移到Linux上的中间件都可以套用。
5. 上线后我建议你这样验证和运维
5.1 两小时观察期到底要观察什么
迁移切换完成后的头两个小时最紧张。不要只盯着进程状态,我一般会开三个窗口:一个看日志、一个看端口、一个看消息收发。
日志重点关注有没有异常堆栈、连接拒绝、磁盘写满这类错误。用systemd托管之后直接:
bash复制journalctl -u apollo-broker -f
端口检查用ss,确保消息端口和管理台端口一直处于LISTEN状态。消息收发更直接,让业务方往固定测试队列写一批数据,实时消费验证,同时看管理台上这个队列的Enqueue和Dequeue计数是否在增长。
另外要盯一下内存和GC。Java进程跑一段时间后堆内存使用会稳定在一个水位,如果持续上涨且一次GC后收不回来,要考虑是不是有消费者客户端泄漏连接,或者队列积压异常。可以临时打开GC日志落盘,观察G1的Mixed GC频率:
bash复制-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/opt/apollo/logs/gc.log
两小时没有明显异常后,才建议把Windows上的旧服务正式停掉。注意是“停掉”不是“删除”,至少保留一周的备份目录,万一Linux侧暴露出Windows上压了很久的问题,还能回滚。
5.2 数据备份策略
Apache Apollo不再有新版本,意味着官方不会修bug、不会出安全补丁,所以备份策略要做得比正常中间件更保守一点。我现在的做法是每天凌晨停掉broker之后打包data目录,保留最近7天压缩包,同时把etc配置文件纳入版本管理。如果你不能接受每天停一次服务,就用文件系统快照或者云平台的磁盘快照,但无论如何不能裸奔。
还有个冷门细节:Apollo的data目录里可能有大量小的store文件,直接tar打包会很慢。可以先把文件做个层次式校验,只对变化的分区做增量备份,或者干脆给数据目录单独挂一块磁盘,备份时做LVM快照,这样对运行中的broker副作用最小。
5.3 运维常用命令速查
很多从Windows过来的同事到了Linux第一反应是慌,因为Windows的服务管理、端口查看、日志查看完全是另一套逻辑。我列几个迁移后最高频用到的命令,贴在服务器旁边比背手册强:
bash复制systemctl status apollo-broker
journalctl -u apollo-broker -n 200
ss -lntp | grep 61680
df -h /opt/apollo
free -g
ps -ef | grep apollo
这几条命令覆盖了服务状态、日志、端口、磁盘、内存、进程六类核心信息。遇到任何异常,先跑一遍这六条,基本能定位80%的问题。
5.4 长期演进建议
如果这只是一次临时救火,迁完就万事大吉,那后续还是会有隐患。Apache Apollo这个项目本身很多年没有新版本了,生态基本停滞。如果公司对消息中间件有长期依赖,我的建议是系统规划一下替换成仍然活跃维护的消息服务,比如Apache ActiveMQ Artemis,然后通过队列桥接逐步切换流量。
但这里要泼一盆冷水,不要为了“跟上技术潮流”而强行把Apollo一夜换成新中间件。老系统只要能稳定跑,迁移到Linux已经大幅提升可用性了。先把Apollo的运维、监控、备份补起来,再腾出时间讨论演进,才是稳妥的节奏。我见过太多人迁移完马上重写一套,结果三个月后生产事故更多,得不偿失。
回到这次迁移本身,我个人在实际操作中的体会是:跨平台迁移最难的部分从来不是拷贝文件和敲命令,而是迁完之后怎么证明新环境和旧环境一样健康。所以在预演阶段多花一小时对比数据、跑完整收发流程,比上线后慌慌张张排查强一百倍。最后再分享一个小技巧:迁移完登录Apollo管理台,把虚拟主机页面、队列列表页面都截图留底,和Windows时代的截图放一起对比。消息数量、消费者数量、订阅关系逐项勾选核对,确认一模一样再切换全部流量。这方法很简单,但能让你在业务方来问“到底有没有问题”的时候,有底气地给出答案。
