服务器入坑指北:开机为什么这么慢

举报
Tulpartner 发表于 2026/10/23 07:53:04 2026/10/23
【摘要】 重启慢,往往不是某个服务自己耗时太久,而是最长依赖链把整机拖住了。本文从分段测量入手,教你用关键链区分“谁在等谁”和“谁自己慢”,并看清耗时排行的盲区。结论很明确:排行只适合找可延迟的支线,判定瓶颈要看关键链;优化应先缩短关键链,再处理支线,并用同类冷启动复测验证。

重启一次要等好几分钟,登上去看"耗时排行",把排在前面的几个服务改成延迟启动、或者干脆禁用——再重启,时间几乎没变。还有更让人费解的:某个服务单看只花了一瞬,整机却慢。
原因在启动的方式本身:启动是高度并行的,整机时长取决于最长的那条依赖链,而不是"谁自己花得久"。所以"谁自己慢"和"谁在等谁"是两个不同的问题——耗时排行只回答前一个,而拖住整机的往往是后一个。
这篇的顺序是:先分段看时间花在哪一段 → 再看关键链找堵点 → 最后才用排行找可延迟的支线;两个工具各自的盲区也一并讲清。全文不给"多少秒算正常"这类阈值。

先分段:时间花在哪一段

启动过程是有阶段的:固件 → 引导加载器 → 内核(可选挂载早期启动镜像)→ 早期启动镜像接管 → 交给根文件系统上的系统管理器。对应的测量把时间分成三段:内核段、早期启动镜像段、应用侧段(工具输出里写作 userspace)。

systemd-analyze time                      # 分段概览:三段各占多少

判据很直接:哪一段占大头,决定下一步查什么。

  • 内核段或早期启动镜像段占大头 → 问题不在服务与单元上,往引导配置、镜像内容、硬件等待这些方向查;
  • 应用侧段占大头 → 才轮到单元与它们之间的依赖关系。
    这里必须写清测量口径的限定:这些数字只测到"所有服务被拉起"为止。它不等于服务已经初始化完成——很多服务被"拉起"之后还要花时间才能对外可用;它也不代表磁盘已经空闲,启动末尾仍在补写的动作不会体现在这个数字里。

关键链:为什么总时长取决于最长的那条链

机制层面:启动过程高度并行,各个目标到达的先后顺序并不确定,但仍然遵守有限的先后结构。并行的含义就是——总时长由最长的那条依赖链决定。一条链上只要有一处在等,后面的都会跟着往后排,这些等待会串起来;而链外的单元就算自己慢,只要不在链上,就不影响总时长。

systemd-analyze critical-chain              # 整机的关键链(时间关键链的树)
systemd-analyze critical-chain <单元>       # 也可以只看某个单元自己的关键链

输出里两个符号的含义必须记住:

  • @ = 该单元到达活动(启动)的"时刻";
  • + = 该单元"自己"启动花的时间。
    于是读法就一句话:"谁在等谁"看 @,"谁自己慢"看 +。
    这条读法能解释一个常见困惑:链上某个单元自己只花了一点点,却排在很后面——它往往才是真正的堵点前驱,因为它前面有什么东西把它卡住了,而它又卡住了后面一串。顺着 @ 往回找"先到的那一个",比对着 + 的大小排名有用得多。
    同样要记住这个工具的限定:它不包含从未进入"启动中"状态的单元(例如设备类单元,直接从"未激活"变成"活动"),也不显示作业信息(尤其是超时的作业)。

耗时排行只能当辅助:它的三类盲区

systemd-analyze blame                       # 按单元初始化耗时排序
systemd-analyze plot > boot.svg             # 启动图:时间线可视化;输出路径自选,但目录要可写(只读目录或权限不足会直接失败);产物是 SVG,用浏览器打开
systemd-analyze dot --order                 # 依赖图:只看"先后依赖"
systemd-analyze dot --require               # 依赖图:只看"需要关系"

耗时排行按"单元初始化耗时"排序,看起来最像"排行表",但官方文档自己就警告它会误导:某个服务的初始化看起来慢,可能只是因为它在等另一个服务完成初始化。
三类盲区必须一起记住,否则一定会误判:

  1. 不显示简单型(Type=simple)服务——系统认为这类服务"立即启动",所以它们根本不会出现在排行里,哪怕它们在背后做了很多事;
  2. 只统计处于"启动中"状态的时间,不反映作业在队列里的等待;
  3. 不能准确反映"等待硬件"造成的延迟。
    这两类限定其实是同源的:关键链与耗时排行都依赖"启动中"状态,也都不反映作业排队——它们是同一件事的两个侧面,不必当成两条不同的坑。
    所以它的正确用法是:找可延迟、可按需化的支线单元,而不是"判定瓶颈"。判定瓶颈要回到关键链。
    顺带一个使用细节:启动图基于最近一次每单元的计时数据,启动后尽早看更准确。

现象到动作的判定表

你看到的 结论 动作
内核段或早期启动镜像段占大头 问题不在服务与单元 往引导配置、镜像、硬件等待方向查
应用侧段占大头,且关键链上有很长的 + 这个单元自己慢 查它自己的初始化动作与外部依赖
关键链上 + 都很短,但 @ 一个比一个晚 有人在等别人,堵点在链条上游 顺着 @ 往前找"先到的那个"
排行里有个大项,但它不在关键链上 它在支线 可以延迟或按需化,但别指望缩短总时长
总分没变,关键链却变了 改的是支线,或基线不可比 回到基线纪律,用同类启动重测

两条优化路线的取舍

路线一:改非关键单元——延迟启动、按需触发、调整依赖顺序。它改善的是观感与资源竞争(启动过程更清爽、争抢更少),但不缩短关键链,也就不改变总时长。代价小、风险低,但它不解决"开机慢"这件事本身。
路线二:真正缩短关键链——把依赖改成并行(在"需要关系"允许的前提下)、把"等外部事件"的单元改成事件驱动、把不必要的等待目标从关键链上摘掉。只有这条会改变总时长,代价是它动的是依赖结构:改动前必须用依赖图确认安全,改完必须复核功能。
这两条不是二选一,而是有先后:先把关键链缩短,再考虑把支线延迟。顺序反了就会出现开头那种情况——关了排行上最大的几项,开机时间没变。
依赖图有两种限定,别混着看:**“先后依赖"回答"谁排在谁前面",“需要关系”**回答"谁真的需要谁”。判断"这两个单元能不能并行"要用后者;把两者混为一谈,就会把一条只是"顺序凑巧"的关系当成硬依赖。

基线纪律与验证

基线纪律:用一次冷启动的测量做基线,任何变更之后再测一次;而且只在同一类启动之间比较(冷启动比冷启动),否则你会把正常波动当成优化效果。
验证清单,每项一条判据:

  • 同一类启动下总分是否变短:与基线同口径对比。
  • 关键链是否变短:如果总分没变而关键链变了,要能解释清楚——是改动了支线,还是两次测量的口径不同。
  • 被延迟或按需化的单元功能是否仍正常:延迟启动最容易踩的坑是"需要它的时候它还没起来"。
  • 重启后服务可用性复核(含自启):确认该起来的服务都起来了,而且是在预期的时间点可用。
  • 回滚路径:改动依赖或延迟启动之前,记下原配置;验证不通过就还原并再测一次。
  • 验证方式固定成一句话:用同一类启动复测一次,总分与关键链的变化能与改动对得上,才算通过。

这篇不解决什么

  • 不展开单元语义:单元的类型与重启策略、就绪判定这些内容属于单元语义层;本篇在启动路径与关键链层。
  • 不决定"该不该重启":那是另一个问题(例如内核更新之后的重启决策);本篇解决的是"重启为什么慢、慢在哪一段"。
  • 不处理运行期等待:运行期的"进程在等谁"属于另一条线;本篇是启动期的关键链——同一个"谁在等谁"的思路,用在两个不同阶段。

踩坑清单

  • 只看耗时排行就动手:排行会误导,慢可能只是"在等别人"。
  • 关掉排行里最大的项却发现没变快:它在支线上,本来就不影响总时长。
  • 把"服务被拉起"当成"服务已就绪":分段数字只测到拉起为止。
  • 忽略简单型服务不出现在排行里:它们可能做了很多事,却完全不计入。
  • 拿一次测量当基线,或在不同类启动之间比较:会把波动当成效果。
  • 改了依赖却没验证功能仍正常:延迟启动最容易造成"需要时还没起来"。
  • 把硬件与早期启动的等待算到服务头上:那一段的问题不在单元里。
  • 只延迟启动、不缩短关键链,却宣布优化完成:总时长没变。
  • 看依赖图不加限定:把"先后"和"需要"混为一谈,会误判能不能并行。
  • 忽略关键链的限定:它不包含从未进入"启动中"的单元,也不显示超时作业。
  • 关键链上只看 + 的大小:@ 才回答"谁在等谁"。
  • 改完不复测、也不留回滚:没法判断改动是否真的有效。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。