你在一台机器上跑一个下载工具,数据目录放在一块单独的数据盘上。平时一切正常。某天机器重启后,你打开 Web 面板,发现所有已经下完的任务,全都变成了"文件丢失 + 进度 0%"。你吓得去文件系统里翻,文件好端端地摆在那里,一个都没少。手动重启一下服务,又全恢复了。
现象
重启后,下载工具里:
- 每个任务的进度都归零,状态标记成"文件丢失""missing"或类似的字眼;
- 文件检查时,明明文件就在
数据盘/下载目录/下,工具却说找不到; - 只要手动
systemctl restart <下载工具>,一切立刻恢复正常——文件"回来了",进度也回来了。
这个"手动重启就好"的特征,是整条线索里最重要的信号。
根因
问题出在开机时的启动顺序上:服务比数据盘的挂载,先启动了。
开机流程是这样排队的(大致):
- 内核启动,探测硬件;
- systemd 拉起各类服务,其中就包括你的下载工具;
- 挂载各类文件系统,其中就包括那块数据盘。
如果第 2 步没等第 3 步,那么在下载工具启动的那一刻,数据盘还没挂上。这时 /数据盘/下载目录/ 这个路径是什么?是根分区上的一个空目录(mount point 本身,内容为空)。
于是下载工具启动时扫描所有任务的文件,发现在它以为的路径下一个文件都没有,就尽职尽责地把这些任务全部标记成"文件丢失"。等它标记完、过一会儿数据盘才挂上来,文件其实已经在了——但工具已经认定"丢了",不会再自动回头复查。
这就是为什么手动重启能修好:那时数据盘已经挂上了,工具重新扫描时看到了真实文件。
有人会说"我明明加了 After=network-online.target 啊"——不够。网络就绪和文件系统挂载是两件不相干的事,等网络不等于等挂载。
解决
在服务的 systemd 单元里,声明"等这个挂载点就绪":
[Unit]
Description=Download tool
After=network-online.target
Wants=network-online.target
# 关键:告诉 systemd,本服务依赖这个挂载点
RequiresMountsFor=/数据盘/下载目录
RequiresMountsFor= 的语义正是"我要用到这个路径,启动前请确保它对应的文件系统已挂载"。systemd 会据此把它排到挂载完成之后。
改完:
systemctl daemon-reload
systemctl restart <下载工具>
然后真的重启一次机器来验证,别只 restart 服务:
systemctl reboot
# 起来后:
systemctl status <下载工具>
确认任务不再变"文件丢失",才算修好。
另外,如果这块盘是写在 /etc/fstab 里的,建议给它加上 nofail 选项:
UUID=xxxx-xxxx /数据盘 ext4 defaults,nofail 0 2
nofail 的意思是"如果这块盘挂不上,也照常开机,别卡进紧急模式"。它和 RequiresMountsFor 配合很合适:盘在就正常等,盘真的坏了也不会把整台机器拖在启动阶段。
延伸与预防
这条坑的教训可以推广到所有"服务依赖外部资源就绪"的场景:数据库文件在某个盘上、日志写到某个挂载点、配置读自某个网络共享……只要那个资源是"后到的",服务启动太早就会看到错误的世界。
正确的做法是用依赖表达出来,而不是靠运气:
- 依赖挂载点 →
RequiresMountsFor= - 依赖数据库 →
After=postgresql.service+Requires= - 依赖网络真的能通 →
network-online.target - 依赖某个一次性任务完成 → 那个任务自己的
.service+After=
养成一个习惯:每当一个服务"手动重启就好、开机启动就坏",第一反应就是查它的启动顺序依赖。这个特征几乎能直接锁定病因,也几乎总是能用 systemd 的依赖语法修好。