1 为什么「进程」还要「服务」
前面的课程已经说明,进程是正在运行的程序,线程是进程内部执行任务的路径,操作系统负责分配 CPU 时间与内存。现在把视角从「它怎样运行」移到「它怎样持续提供能力」:订单 API 在终端里启动后,谁记住启动命令?终端断开后它还在吗?程序异常退出后谁发现?主机重启以后,谁按正确的身份与配置把它重新拉起来?这些问题共同构成服务管理。
进程描述某次运行的实体,服务描述需要维持的职责。今天的订单 API 进程编号可能是 2841,明天重新启动以后可能是 6012,但「接收订单请求」的职责没有改变。运维需要围绕稳定的服务名称操作,而不能把某个 PID 当成永久身份。服务管理器把名称、启动方式、权限、状态、退出结果和恢复策略连接起来,让短暂存在的进程成为可以重复管理的对象。
本课使用 Linux 上常见的 systemd。它管理的对象叫 unit,可以表示服务、挂载点、定时任务等;本课聚焦后缀为 .service 的服务单元。order-api.service 是我们给订单 API 约定的名称。管理员声明程序在哪里、以谁的身份运行、失败以后怎么办,systemd 按声明执行,并保存它观察到的运行结果。不同发行版可能有不同默认配置,命令输出中的细节也会变化,但这套职责划分可以迁移。
直接在终端执行程序,适合开发和临时诊断。使用 & 放到后台,只是改变 shell 等待命令的方式;使用 nohup 可以处理部分终端断开问题,却不会自动得到开机启动、失败恢复和统一状态查询。脚本也能尝试循环拉起程序,但脚本本身由谁管理、停止时如何处理子进程、日志写到哪里,仍要另行解决。把这些规则交给统一管理器,可以减少每个应用各自发明管理流程的成本。
还要分清 3 个观察层次:进程存在,表示操作系统中有执行实体;unit 为 active,表示管理器认为它处于运行状态;业务可用,表示请求能够得到符合要求的响应。订单 API 即使进程尚在,也可能因为数据库连接耗尽而无法下单。服务管理解决启动、监督和恢复的基础问题,业务探测仍然必要。后面看到绿色状态时,要记得它说明的是管理器的观察范围。
本课沿用同 1 个订单 API:程序位于 /opt/order-api/bin/order-api,工作目录为 /var/lib/order-api,使用专用账户 orderapi,以前台方式持续运行。它把日志写到标准输出或标准错误,遇到无法继续工作的错误时返回非零退出码。这里的路径和账户都是示例,应用安装、账户创建与目录授权需要先完成,不能期待 unit 自动生成这些前提。

2 unit 文件直觉:ExecStart、Restart、User
把 unit 文件看成服务的运行说明书,而不是安装程序。系统管理员创建的服务文件通常放在 /etc/systemd/system/。下面的配置适合作为本课的起点,实际使用前需要确认程序支持这些路径与运行方式。示例中不把数据库密码写进命令行;配置和凭据的提供方式应遵循应用自己的约定。
# /etc/systemd/system/order-api.service
[Unit]
Description=Order API
StartLimitIntervalSec=60s
StartLimitBurst=5
[Service]
Type=simple
User=orderapi
Group=orderapi
WorkingDirectory=/var/lib/order-api
ExecStart=/opt/order-api/bin/order-api
Restart=on-failure
RestartSec=3s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
[Unit] 描述单元自身以及与其他单元的关系;[Service] 描述进程如何运行;[Install] 描述执行 enable 时建立哪些安装关系。它们不是按顺序执行的脚本段落。配置项所在的段落有意义,拼错名称或放错位置可能导致设置没有按预期生效,因此保存文件不代表配置已经正确投入运行。
ExecStart 指定启动命令。优先使用可执行文件的绝对路径,减少管理员交互环境与服务环境不同造成的歧义。这里的命令不会默认经过 shell 解释,|、>、&& 也不会自动变成管道、重定向和条件执行。若确实需要 shell,应明确调用并处理引用,但不要为了熟悉而把所有启动逻辑塞进复杂脚本。运行说明越直接,退出状态越容易解释。
Type=simple 适用于程序留在前台运行的情形。systemd 启动它以后便可继续推进依赖它的启动任务,不会等待订单 API 完成数据库连接或开始接受请求。不要在命令末尾加 &,也不要同时要求应用自行转入后台,否则管理器对主进程的跟踪可能与实际程序模型不符。若程序提供了就绪通知能力,需要结合其文档选择相应类型,本课不展开通知协议。
User 和 Group 确定运行身份。专用账户应拥有所需的数据目录权限,而程序目录通常只需要可读与可执行。应用以普通账户运行,不能随意修改系统文件,这是限制故障影响的基础。某次启动报权限错误时,应检查相关路径每层目录的穿越权限与目标文件权限,不应直接删去 User 把服务改成高权限运行。工作目录也由配置明确指定,不能假设服务继承管理员终端所在的目录。
Restart=on-failure 表示在管理器判定运行失败时尝试恢复,例如主进程非零退出、被某些异常信号终止或超时。RestartSec=3s 给自动重启安排等待时间,它不是错误修复动作。若程序把致命错误错误地报告为退出码 0,这个策略可能不触发。Restart=always 还会覆盖主进程正常退出的情形,适合本来就应持续运行的程序,但管理员明确执行 stop 时,正常的自动重启策略不会把它立即拉回。
限速项防止短时间无限重启。本例限制 60 秒内允许的启动次数,达到限制后可能以 start-limit-hit 停住,等待排障与后续启动操作。等待时间、退出原因与启动计数会共同决定最终行为,不要把 Restart 理解成「无论如何永远保持在线」。服务状态也会经历 inactive、activating、active、failed 等阶段;自动恢复等待时可能显示 activating (auto-restart),短暂失败不一定停留在 failed 页面上。

3 systemctl:启停与状态
systemctl 是向 systemd 查询与发出管理请求的常用入口。修改系统服务需要相应管理权限;以下以 sudo 表示获得授权的管理员操作,读取状态与日志是否需要额外权限则取决于系统配置。刚写入或修改 unit 后,先让管理器重新读取定义,再启动或重启需要应用新配置的服务。
sudo systemctl daemon-reload
sudo systemctl start order-api.service
systemctl status order-api.service --no-pager
daemon-reload 更新管理器掌握的 unit 定义和依赖关系,不会自动让运行中的程序换成新版本,也不会替程序重新读取业务配置。start 请求启动;服务已在运行时,通常不会因此再开 1 份相同实例。修改 ExecStart 或运行身份后,旧进程仍保持旧的启动条件,需要在合适的变更窗口重启,才能让新进程使用新的定义。
status 是适合人阅读的摘要。先看 Loaded,判断文件是否找到、从何处加载及是否 enabled;再看 Active,判断当前运行阶段与持续时间;然后看主进程信息、退出码和最近的日志。摘要里的日志通常只是尾部片段,不能替代完整事件记录。enabled 是安装关系,active 是运行状态,2 者可以独立组合。运行中的服务可以没有开机自启,开机自启的服务也可以已经失败。
sudo systemctl stop order-api.service
sudo systemctl restart order-api.service
systemctl is-active order-api.service
systemctl is-enabled order-api.service
systemctl show order-api.service -p MainPID -p Result -p NRestarts
stop 请求有管理地停止服务。一般先发送终止信号,给应用处理收尾的时间;超过停止超时后,管理器可能强制结束剩余进程。应用应当响应停止请求,停止接收新任务并处理正在进行的工作。restart 将停止与重新启动串起来,因此可能带来服务中断,不能把它当成没有代价的刷新按钮。对订单提交这样的操作,还要考虑客户端重试和重复请求的处理。
reload 则依赖应用及 unit 提供配置重载能力。本课示例没有配置 ExecReload,不能假定执行 reload 就会成功,更不能假定它会应用所有 unit 变更。判断动作是否正确,要看期望改变的是管理器的定义、应用读取的配置,还是正在执行的程序文件。把这 3 类变化混为 1 类,是「改了却没生效」的常见来源。
用于脚本时,优先依据专用查询命令的退出状态或明确属性,不要从面向人的彩色摘要中截取某行文字判断成功。即使 is-active 返回 active,也只说明当前管理状态;还应调用受控的健康检查,确认应用完成初始化并能访问必要依赖。启动后若数据库连接失败,进程可能立即退出,也可能留在等待重试的状态,2 种情况的后续处置不同。
执行操作前后都应留下时间与目的。先记录当前状态和失败信息,再决定是否重启;重启完成后重新观察状态、日志和业务请求。systemctl reset-failed 可清除失败标记和相关启动限速计数,但它既不会修复配置,也不会自动启动服务。只有根因已经处理、确实准备再次尝试时,才应把它放进恢复步骤,避免清理了证据却保留了故障。
4 journalctl:按服务看日志
服务能自动恢复,不表示问题可以不追踪。journald 接收日志并组织成 journal,journalctl 负责查询。对本课的订单 API,标准输出与标准错误会被管理器接到 journal,因此不必为了基本查询而先设计单独的文件重定向。日志记录还带有时间、unit 等元数据,排障时可以逐步收窄范围,减少其他服务的信息干扰。
journalctl -u order-api.service -n 100 --no-pager
journalctl -u order-api.service --since "2026-10-30 01:00:00" --until "2026-10-30 01:20:00"
journalctl -u order-api.service --since "30 minutes ago" -p warning
journalctl -u order-api.service -f
先用 -u 锁定单元,再用 --since 与 --until 对齐故障时段,最后按需要增加优先级条件。-n 100 只取最近 100 条,适合快速查看;-f 持续跟随新记录,适合观察 1 次受控启动,按 Ctrl+C 退出查询不会停止服务。调查某个时刻的故障时,要确认主机时区与告警时间使用的时区一致,不能仅凭屏幕上相似的时分就认定是同 1 次事件。
-p warning 会选择 warning 及更严重的级别,而不是只匹配 warning。这里的优先级属于日志元数据,应用打印字符串 ERROR 不一定让 journal 把该条记录标记为 error。普通标准输出中的业务错误可能仍以默认级别收集。因此优先级过滤适合缩小已知问题,发现结果为空时,应撤掉该条件查看完整时间段,不能据此断言服务没有错误。
应用日志与管理器日志通常要结合阅读。应用可能记录「无法打开配置」,管理器随后记录主进程退出和恢复安排;它们相邻却表示不同层面的事实。若进程来不及输出信息便退出,仍可从退出状态、信号和启动错误获得线索。若应用把日志写进自己的文件,则 journal 可能只有管理器事件,需要继续按照应用配置找日志去向,不能认为 -u 会自动收集任意文件。

主机重启也是重要边界。journalctl --list-boots 可以列出当前保留的启动记录,journalctl -b -1 -u order-api.service 查询上次启动的相关日志;前提是那些记录确实保留下来。某些系统默认只使用易失性存储,重启以后先前 journal 就无法查询。journalctl --disk-usage 可以查看当前日志占用,但占用量本身不能保证覆盖了需要的历史时长。
需要跨重启排查时,应检查 journald 的实际存储策略,按系统文档启用持久化,并设置合适的空间、保留期限和外部汇集方案。Storage=persistent 是相关配置项之一,修改配置后还要按系统要求让配置生效,并验证新的记录确实写入持久存储。磁盘空间有限时,轮转和清理仍会淘汰旧记录;持久化表示存储介质选择,不表示永久保存。
订单日志还应避免输出密码、令牌和完整敏感请求体。为了定位问题,可以记录经过筛选的错误类型、请求标识和依赖名称。日志应能解释发生了什么,也应控制单条长度与输出频率。错误循环每秒打印大量堆栈时,日志压力本身会消耗资源,并加快旧证据淘汰,后面的重启循环案例会再次遇到这个问题。
5 开机自启与依赖顺序
「现在启动」和「下次开机启动」是不同请求。start 改变当前运行状态,enable 则依据 [Install] 建立关系,让服务在相应目标被启动时进入启动事务。示例的 WantedBy=multi-user.target 表示 enable 时把订单 API 加入这个常用系统目标的需求集合。target 可以理解为组织多个单元的启动节点,本课不要求背诵所有系统目标。
sudo systemctl enable order-api.service
systemctl is-enabled order-api.service
sudo systemctl enable --now order-api.service
sudo systemctl disable order-api.service
单独 enable 不保证当前立即启动;enable --now 同时请求建立安装关系和立即启动。单独 disable 也不停止正在运行的服务,需要停止时应明确执行 stop,或在适用时使用 disable --now。disable 只移除对应安装关系,服务仍可能被其他依赖或人工命令启动。维护操作应写清楚要改变的状态,避免把「已经取消自启」误写成「已经停止运行」。
依赖关系至少有 2 个问题:是否把另 1 个单元也拉进来,以及双方按什么顺序启动。Wants= 表达较弱的需求关系,依赖对象启动失败时,不必因此阻止本单元继续启动;Requires= 表达更强的需求关系,并具有相应失败与停止传播语义。After= 只约束相关单元同时参加启动时的先后次序,不会单独把另 1 个单元启动起来。
例如本机数据库确实由 order-db.service 管理,且订单 API 必须在其启动后再启动,可以根据实际容错设计,把 Requires=order-db.service 与 After=order-db.service 放在订单 API 的 [Unit] 段。这里的单元名称是示例,需要先确认数据库真实的管理方式。若数据库位于远程主机,虚构 1 个本机数据库依赖并不能表达远程服务是否可用。

网络相关配置也有类似边界。需要等待网络配置阶段时,可考虑同时使用 Wants=network-online.target 与 After=network-online.target,并检查系统相应等待服务的配置。该目标的含义取决于本机网络管理实现,它不保证订单 API 的数据库、支付接口或域名解析永远可用。启动后的连接中断更不会靠开机顺序自动修复,应用仍需合理的连接超时与重试。
即使数据库服务先达到 active,也未必已经能接受查询,原因与 Type=simple 的状态边界相同。可靠设计把「启动顺序」与「业务就绪」分别处理:管理器负责组织启动,应用负责判断依赖是否达到可用条件。重试应有间隔和上限,不能在等待依赖时占满 CPU。健康检查则应说明服务是否已经具备承接请求的能力。
启用后应安排受控的重启演练,在有回退办法的环境中检查实际启动结果。演练不是只看 is-enabled,还要确认服务能用预期身份运行、工作目录权限正确、配置来源有效、日志能够跨重启查询,最后验证 1 次安全的业务请求。能从管理员终端启动,只证明当时的人工环境满足条件;能在开机环境中恢复,才说明运行说明比较完整。
6 资源限制入门:MemoryMax / CPUQuota 直觉
管理器除了决定是否运行,还能给服务设置资源边界。systemd 在底层利用操作系统的资源分组机制,将服务及其后代进程归入可管理范围;这与资源组有关,本课只建立直觉,不展开控制器细节。边界的目的是约束故障影响,并让容量需求可以讨论,不是替代程序优化,也不是保证业务一定及时完成。
# 放在该服务的 [Service] 段,数值仅供容量讨论
MemoryMax=512M
CPUQuota=50%
MemoryMax=512M 给服务设置内存使用的硬上限。这里管理的是该服务分组中被计入的内存,不是给应用预留 512M,也不能简单等同于主进程某个内存统计列。接近上限时可能发生回收压力;无法在限制内满足需求时,可能触发内存不足处理并杀死相关进程。实际表现要结合内核、systemd 版本和服务行为观察,不能假定超限会以温和、可预测的应用报错结束。
对订单 API 来说,若正常流量下已经需要接近 512M,照抄示例就可能制造周期性退出。合理做法是先观察常态、峰值、初始化阶段和短时突发,再留出余量并验证。内存限制解决的是「最多允许占用多少」,内存泄漏解决的是「为什么持续增长」,2 个问题都要处理。设置上限后再开启自动恢复,只会把持续增长变成重复启动,不能证明泄漏消失。
CPUQuota=50% 的分母是 1 个逻辑 CPU 的时间预算,可以直观理解为在持续争用期间,整个服务平均最多使用约半个逻辑 CPU 的计算时间。它不是整台主机 CPU 总量的 50%,也不是把进程永久固定到某个核心。CPUQuota=200% 可以表示约 2 个逻辑 CPU 的时间预算;是否用得上,还取决于程序并行能力与主机可用资源。
配额耗尽后,服务需要等待后续预算,表现可能是请求延迟升高、队列变长和吞吐量下降。主机还有空闲 CPU 时,受到配额约束的服务也可能无法继续加速。因此看到延迟升高时,要同时看负载、配额与应用排队情况。限制是保护边界,过紧的边界也会成为故障原因,尤其当启动过程包含较重的初始化计算时。

资源配置与重启策略还会相互影响。服务因内存压力退出后被重新拉起,每次初始化又分配大量内存,可能形成故障循环;CPU 配额太低则可能拉长初始化时间,进一步触发应用或管理器设置的超时。排障时要同时检查资源边界与退出原因,不应看到自动恢复就只调整 RestartSec。对每项限制,都应能回答其容量依据与超限后的处置办法。
修改这些配置后,需要重新读取 unit,并让相应设置按系统支持的方式生效;对本课的持久配置流程,可以在合适窗口重启服务后验证。查看管理器报告的内存、CPU 使用情况,再结合业务延迟和错误率评估效果。实际指标名称、统计可用性与默认记账行为可能随系统版本而异,因此部署清单中应记录版本与验证结果,而不是只保留配置片段。
7 常见坑:杀进程不改 unit、忘开 Restart、日志滚没了
第 1 个坑是把 PID 当成唯一管理入口。管理员看到异常进程,直接 kill,随后发现新 PID 又出现,便认为「怎么杀不掉」。实际上管理器可能正在按 Restart 策略恢复。停止服务应优先使用 systemctl stop,因为它表达的是撤销当前运行要求;发送信号表达的是对某次运行的操作。2 者作用层次不同,操作结果也可能不同。
直接杀掉进程也不会修改 unit。若故障来自错误参数、工作目录或权限,下次仍会按原样启动;若程序有子进程,只盯着主 PID 更可能遗漏整体状态。不要把 kill -9 作为首选收尾方式,它不给应用正常清理的机会。确需发送信号时,应明确目的并结合管理器状态观察,而不是连续尝试不同信号掩盖管理关系。
第 2 个坑是忘记配置 Restart,或以为 enable 自带故障恢复。未声明时,常见默认策略是不自动重启,程序退出便停在那里。enable 只关联未来的启动触发,不能处理凌晨发生的进程失败。另 1 种情况是已经写了 Restart=on-failure,程序却在内部异常后以 0 退出;管理器看到正常完成,自然不会按失败恢复。应修正应用退出语义,再评估策略是否覆盖预期事件。
还有人修改了 unit 却忘记 daemon-reload,或只执行 reload 就以为新的启动参数已经生效。排查时可用 systemctl cat order-api.service 查看定义及覆盖片段,用 systemctl show order-api.service -p Restart -p RestartUSec -p User 查看管理器当前理解的相关属性。注意实际输出可能包含覆盖配置,不能只盯住自己刚编辑的某个文件。配置文件、有效属性与运行中的进程需要相互核对。
第 3 个坑是事后才发现日志滚没了。journal 有空间与保留约束,应用文件日志也有自己的轮转策略;重启后的易失性日志、被清理的旧文件以及没有收集的标准输出,都不能凭空恢复。事前应确定故障发现可能延迟多久,再据此规划本地保留和外部汇集。发现磁盘紧张时,也不要先大范围清理再开始调查,应先保存需要的时段与关键上下文。
第 4 个坑是把自动恢复当成监控。若订单 API 每隔 10 分钟崩溃 1 次,每次又迅速恢复,某次状态查询可能恰好全是绿色,但用户已经遇到多次失败。应关注观察窗口内的重启次数、错误率和响应延迟,并把持续恢复本身视为异常信号。NRestarts 可以提供当前管理器上下文中的自动重启线索,但不是永久累计的业务故障账本。
第 5 个坑是为了让服务启动而放开所有权限或资源限制。启动成功只能说明某个阻碍被移走,不能说明改动合理。应把错误定位到具体路径、操作或资源需求,再做最小必要修正。对于订单 API,健康检查成功以后,还要确认数据目录权限、日志可见性和资源消耗符合预期。恢复过程应留下可解释的改动,避免下次故障只能依靠猜测。
8 案例:订单 API 半夜静默退出
凌晨 02:10,订单入口开始返回不可用;早晨值班人员登录后,发现机器能访问,CPU 与内存都不高。过去的启动方式是在终端运行程序后放到后台,没有统一的服务定义。应用曾遇到未处理异常并退出,终端又早已关闭,现场只剩「程序不在了」。这个案例的关键不在于手工再执行 1 次命令,而在于让下一次退出能够被观察、被恢复并留下证据。
先恢复已知事实:故障开始时间来自请求监控,进程消失来自主机观察,退出原因则仍需日志证明。不能因为主机资源平稳,就排除应用内部异常;进程已经退出,资源自然可能恢复平稳。如果旧程序把日志写在未知位置,应沿原来的启动脚本和配置寻找,不要编造「肯定是内存不足」的结论。无法找回的历史证据也应如实记录为未知。
接着把订单 API 纳入本课的 unit。确认专用账户存在,程序可执行,工作目录可访问,应用留在前台并输出日志。写入定义后执行 daemon-reload,再执行 enable --now,分别验证 enabled 与 active。启动日志显示应用加载配置、连接数据库并完成初始化后,再用健康检查和安全的测试请求确认业务能力。若原进程仍在,迁移前应先受控停止,避免端口冲突或重复处理任务。
在预发布环境安排故障演练:让应用按可控方式发生非零退出,记录退出前后的 PID、时间和日志。预期看到管理器识别失败,等待约 3 秒,再尝试启动新的进程;启动和初始化本身还会额外耗时,所以实际不可用时间不等于 3 秒。若连续失败达到启动限速,服务应停住并产生可识别的失败信息,而不是由值班人员误以为恢复机制失踪。

演练还要覆盖管理员 stop。维护时主动停止服务,预期它保持停止,避免把正常维护误判成需要马上恢复的崩溃。随后重新启动,再检查日志是否能把 2 次运行串成时间线。若主机重启后需要继续调查,应提前验证 journal 的持久化与保留能力。演练结果应记录实际观察,而不是把配置中的预期直接写成成功证据。
将同样的配置投入正式环境以后,自动恢复只能缩短部分中断,不能补回所有正在处理的请求。订单请求可能在响应发出前遇到进程退出,客户端因此重试;系统仍需要应用层面的请求标识和重复提交处理。服务管理不会自动提供订单语义上的正确性,也不会自动解决数据库中的半成品业务状态。运维方案应把进程恢复与业务恢复分别定义。
最后建立后续任务:按重启频率和请求失败情况告警,保存退出前后的日志,修复未处理异常,并在相同触发条件下回归验证。若之后又出现「退出后一直没有恢复」,就依次检查有效 Restart 策略、退出码、是否人工停止以及是否触发启动限速。这样,半夜退出从无人知晓的偶发事件,变成有状态、有证据、有责任归属的故障流程。
9 排查案例:restart 循环吃满 CPU
另 1 次发布后,订单 API 的进程编号持续变化,日志每隔很短时间重复出现相同错误,主机 CPU 同时明显升高。有人建议提高 CPUQuota,也有人准备把 Restart 改为 always。此时先提出可以检验的解释:新配置导致启动必然失败,重启策略又让应用不断重复昂贵初始化。资源消耗来自失败路径上的重复工作,增加资源未必能改变失败条件。
先记录故障时间、当前状态和有效配置,再取最近时段的完整日志。观察 NRestarts 在短时间内是否上升,并把每次「开始启动、应用报错、进程退出、安排重启」连成事件序列。进程变化快会让某次快照看不到高占用者,主机总量却仍然偏高,因此要结合持续观察。CPU 升高也可能来自堆栈输出、日志处理或其他服务,不能仅凭重启次数把所有消耗都算给主进程。
systemctl status order-api.service --no-pager
systemctl show order-api.service -p NRestarts -p Result -p Restart
journalctl -u order-api.service --since "10 minutes ago" --no-pager
sudo systemctl stop order-api.service
若确认循环正在放大故障,先受控停止服务,同时按业务预案处理流量和告警。不要在业务环境连续执行 restart 来「碰碰运气」,因为人工启动也可能消耗限速额度,还会增加无效日志。保存必要证据后,在隔离环境复现失败条件。本例最终发现新配置把必需的数据目录写错,程序每次构建缓存后才发现路径不存在,随后以非零退出,下一次启动又从头计算。

RestartSec 能改变恢复节奏。假设每次失败前运行约 1 秒,等待 1 秒再重启,则每轮约 2 秒,1 分钟可能发起约 30 次启动;等待改为 9 秒,每轮约 10 秒,尝试频率显著下降。这里只是没有限速介入的粗略估算,实际还受启动耗时、清理耗时和调度影响。较长等待降低重复负担,也会延后瞬时故障消失后的恢复,需要结合服务目标取舍。
启动限速则提供另 1 道边界:短时间失败过多时停止继续尝试。StartLimitIntervalSec=60s 与 StartLimitBurst=5 的含义是对相应观察区间内的启动请求计数,不是「失败 5 次以后永久禁用」。它也不是指数退避设置。触发限制以后,不能假设区间结束就必然自动恢复;应根据当前状态明确安排后续启动、告警和人工处置,避免服务长期停着而无人跟进。
修正路径后,先验证目录与权限,再重新读取修改过的 unit。若仍受失败标记或启动限速影响,可在准备恢复时执行 reset-failed,随后明确 start。观察足够覆盖原故障触发条件的时间,确认 PID 稳定、自动重启次数不再上升、CPU 回落且业务检查成功。如果只是延长 RestartSec 后 CPU 降低,却仍定期失败,结论应写成「减轻重复负担,根因未解决」。
这个案例给出排查次序:先确认正在发生什么,再限制重复故障的影响,接着修复必然失败的条件,最后恢复并验证。Restart 解决运行中断后的再次尝试,RestartSec 决定尝试间隔,启动限速防止过密尝试,资源限制约束单个服务的影响范围。它们各自有用,但都不能把错误路径、缺失文件或不兼容配置自动变成正确状态。
放进方案里
在订单系统方案中,应把「订单 API 如何作为服务运行」写成可以交接的约定。明确 unit 名称为 order-api.service,记录程序路径、工作目录、运行账户和配置来源;账户只获得必需权限,敏感凭据不进入启动命令与普通日志。应用采用前台运行方式,正确报告退出状态,并响应停止信号,给正在处理的请求留下收尾机会。
恢复策略写清 Restart=on-failure 的适用原因,同时记录 RestartSec、启动限速及触发后的告警责任。说明可接受的恢复等待时间,也说明自动重启无法保证请求不丢失或不重复。对短时间持续失败,应允许服务停住,保留证据并转入人工处置;不要用无限尝试代替明确的故障处理流程。
部署步骤包含重新读取 unit、受控重启、业务检查和回退办法。开机自启单独验证 enabled 状态,并通过演练确认重启主机后能够恢复。依赖部分分别描述启动关系与业务就绪条件;远程数据库不可用时,由应用执行带间隔的重试,健康检查反映实际承接请求的能力。
资源预算根据测量设置 MemoryMax 与 CPUQuota,记录峰值、余量和超限表现。日志部分说明标准输出去向、持久化方式、本地保留范围和外部汇集要求,并让值班人员能按 unit 与时间窗查询。重启次数、错误率和延迟共同参与告警,避免自动恢复掩盖反复崩溃。
验收至少覆盖正常启动、主动停止、异常退出、连续启动失败及主机重启。每项保留状态、日志和业务结果,恢复后继续观察原触发条件。方案最后列出谁负责应用根因、谁负责运行配置、谁在限速停服后接手,使服务管理从命令集合变成有证据、有边界的运行承诺。
10 小结:服务管理清单
服务管理把临时进程组织成可重复启动、可观察、可恢复的运行对象。交付订单 API 前,可以逐项核对以下清单。
- 运行定义:ExecStart 明确,程序以前台方式运行,User、Group、工作目录和权限符合实际需要。
- 配置生效:修改 unit 后已重新读取定义,需要更新运行条件时已受控重启,并检查有效属性。
- 状态判断:区分 active、enabled 和业务可用,能够解释退出码、主进程变化与失败结果。
- 日志证据:能按 unit、时间和启动批次查询,了解优先级过滤的边界,保留策略覆盖排障窗口。
- 自动恢复:Restart 覆盖预期退出情形,RestartSec 与启动限速有依据,限速停住后有人接手。
- 开机与依赖:明确安装关系、启动先后与应用就绪的差别,经过受控重启演练。
- 资源与业务:MemoryMax、CPUQuota 来自容量观察,停止和重启时的请求处理由应用方案保证。
- 故障闭环:保存证据、限制影响、修复根因,再验证状态、日志与业务结果,持续重启本身也会告警。
下一课转向 S12 软件与编译:把本章验收过的结论带进后续主题,写成可检查的方案约束。