news 2026/2/7 12:05:34

多个服务依赖怎么搞?测试脚本教你合理排序

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多个服务依赖怎么搞?测试脚本教你合理排序

多个服务依赖怎么搞?测试脚本教你合理排序

在实际运维和开发环境中,我们经常遇到这样的问题:系统启动时需要按特定顺序启动多个服务——比如数据库必须先于应用服务启动,消息队列要早于消费者进程加载,缓存服务得在业务逻辑之前就绪。一旦顺序错乱,轻则服务启动失败,重则整个系统卡死、日志刷屏、排查耗时数小时。

这个问题看似简单,实则暗藏玄机。很多人以为只要把脚本丢进/etc/init.d/就万事大吉,结果一重启,应用报“连接拒绝”,查半天才发现 MySQL 还没起来;或者发现 Redis 启动了,但应用却连不上,最后发现是网络配置脚本被排在了后面……

别急,这不是玄学,而是有章可循的工程实践。本文不讲抽象理论,不堆概念术语,就用一个真实可用的测试开机启动脚本镜像,手把手带你理清依赖关系、看懂启动顺序逻辑、写出可验证的排序方案。全程基于 Linux 常见发行版(CentOS 和 Ubuntu 均适用),所有操作均可直接复现,无需额外安装工具。

你不需要是系统专家,只要会写 shell 脚本、能敲几条命令,就能掌握这套方法。读完后,你会清楚知道:

  • 为什么有些脚本启动快、有些总报错;
  • 怎么一眼看出哪个服务该先起、哪个得等;
  • 如何用最简方式验证你的排序是否生效;
  • 遇到依赖冲突时,该改哪一行、调哪个数字。

准备好了吗?我们从最基础的脚本准备开始,一步步拆解这个“多服务依赖”的硬骨头。

1. 先写一个能跑起来的测试脚本

别一上来就碰复杂的业务服务,我们先造一个干净、可控、带日志输出的“测试脚本”,专门用来观察启动行为。它不干别的事,只做三件事:打时间戳、写日志、假装自己是个服务。

1.1 创建脚本文件

打开终端,执行以下命令创建/etc/init.d/mytest.sh

sudo tee /etc/init.d/mytest.sh << 'EOF' #!/bin/bash # chkconfig: 2345 99 01 # description: MyTest service for dependency testing case "$1" in start) echo "$(date '+%H:%M:%S') - [mytest] Starting..." | tee -a /var/log/mytest.log sleep 1 echo "$(date '+%H:%M:%S') - [mytest] Started successfully." | tee -a /var/log/mytest.log ;; stop) echo "$(date '+%H:%M:%S') - [mytest] Stopping..." | tee -a /var/log/mytest.log sleep 0.5 echo "$(date '+%H:%M:%S') - [mytest] Stopped." | tee -a /var/log/mytest.log ;; restart) $0 stop $0 start ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 ;; esac exit 0 EOF

1.2 设置执行权限并验证

sudo chmod +x /etc/init.d/mytest.sh sudo /etc/init.d/mytest.sh start

检查日志是否生成:

tail -n 3 /var/log/mytest.log

你应该看到类似这样的输出:

14:22:05 - [mytest] Starting... 14:22:06 - [mytest] Started successfully.

成功!这个脚本现在就是一个“可观察的服务”——它不依赖任何外部组件,但能清晰告诉你:它什么时候启动、有没有卡住、是否真的被执行了。

小贴士:为什么不用systemd?因为本文聚焦传统 SysV init 的启动顺序机制,这是理解依赖排序最底层、最直观的入口。Ubuntu 16.04+ 和 CentOS 7+ 虽默认用systemd,但/etc/init.d/兼容层依然完整,且rcN.d的软链逻辑完全一致,不影响学习本质。

2. 看懂系统启动级别和执行目录

很多同学卡在这一步:明明写了脚本、加了软链,重启后却没反应。根本原因,是没搞清“系统到底在哪一刻、从哪个目录里找你的脚本”。

2.1 查当前运行级别

执行命令:

runlevel

输出类似:

N 5

这表示:系统当前运行级别是5(图形界面模式)。注意,这里的5就是关键线索——它决定了系统启动时去哪个目录加载脚本。

小白友好解释
Linux 启动不是“一股脑全拉起来”,而是分阶段、按“级别”来。级别3是纯命令行多用户模式,5是带图形界面的多用户模式。每个级别对应一个专属目录:/etc/rc3.d//etc/rc5.d/……系统启动时,就去对应目录里,按名字顺序执行所有以S开头的脚本。

2.2 理解/etc/rc5.d/目录的命名规则

进入该目录看看:

ls -l /etc/rc5.d/ | head -10

你会看到一堆类似这样的文件:

S10rsyslog S20network S50apache2 S99mytest K01mysql

重点来了——这些名字不是随便起的,它们自带两层含义:

  • 首字母S表示 Start(启动),K表示 Kill(停止)
  • 紧跟的两位数字:表示执行顺序,范围0199数字越小越早执行,越大越晚

所以S10rsyslog一定比S50apache2先跑,而S99mytest是这一批里最后一个启动的。

为什么要有 K 开头的?
这是为了关机或切换运行级别时用的。比如从5切到3,系统会去/etc/rc3.d/K开头的脚本,按数字从小到大执行停止逻辑。你暂时不用管它,专注S就行。

2.3 关键结论:依赖 = 启动序号的大小关系

到这里,你就掌握了核心逻辑:
如果 A 服务依赖 B 服务(比如应用依赖数据库),那么 A 的启动序号必须大于B 的启动序号。
比如数据库叫S20mysql,你的应用就得叫S80myappS99myapp,不能叫S15myapp

这就是“合理排序”的全部秘密——没有魔法,只有数字大小。

3. 给测试脚本加个“依赖伙伴”,模拟真实场景

光一个脚本看不出依赖效果。我们再加一个“数据库模拟脚本”,让它启动得早一点,然后让mytest.sh显式等待它——这样就能验证排序是否真起作用。

3.1 创建数据库模拟脚本

sudo tee /etc/init.d/mydb.sh << 'EOF' #!/bin/bash # chkconfig: 2345 20 80 # description: MyDB mock service (starts early) case "$1" in start) echo "$(date '+%H:%M:%S') - [mydb] Starting database mock..." | tee -a /var/log/mydb.log sleep 2 echo "$(date '+%H:%M:%S') - [mydb] Database ready." | tee -a /var/log/mydb.log ;; stop) echo "$(date '+%H:%M:%S') - [mydb] Stopping database..." | tee -a /var/log/mydb.log sleep 0.5 echo "$(date '+%H:%M:%S') - [mydb] Database stopped." | tee -a /var/log/mydb.log ;; restart) $0 stop $0 start ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 ;; esac exit 0 EOF sudo chmod +x /etc/init.d/mydb.sh

注意看chkconfig行里的20—— 这就是它的启动序号,比mytest.sh99小得多,意味着它会先启动。

3.2 修改 mytest.sh,加入依赖检查逻辑

编辑/etc/init.d/mytest.sh,在start)分支开头加一段等待代码:

sudo sed -i '/start)/a\ # Wait for mydb to be ready\n timeout=30\n while [ $timeout -gt 0 ]; do\n if grep -q "Database ready." /var/log/mydb.log 2>/dev/null; then\n echo "$(date \'+%H:%M:%S\') - [mytest] Detected mydb is ready." | tee -a /var/log/mytest.log\n break\n fi\n sleep 1\n timeout=$((timeout - 1))\n done\n if [ $timeout -eq 0 ]; then\n echo "$(date \'+%H:%M:%S\') - [mytest] ERROR: mydb did not start in time!" | tee -a /var/log/mytest.log\n exit 1\n fi' /etc/init.d/mytest.sh

这段代码的意思是:启动mytest时,最多等 30 秒,反复检查/var/log/mydb.log里有没有 “Database ready.” 这行字。如果超时没等到,就直接退出报错。

现在,两个脚本有了明确的依赖关系:mydb.sh(S20)必须先于mytest.sh(S99)启动,且mytest.sh会主动确认依赖就绪。

4. 创建软链接,正式“排序”

前面都是准备,现在进入最关键的一步:把脚本放进正确的rcN.d目录,并用数字控制顺序。

4.1 进入对应 rc 目录

根据runlevel输出的级别(比如5),进入:

cd /etc/rc5.d/

4.2 创建软链接(带序号)

mydb.sh创建启动链接(序号20):

sudo ln -sf /etc/init.d/mydb.sh S20mydb

mytest.sh创建启动链接(序号99):

sudo ln -sf /etc/init.d/mytest.sh S99mytest

注意ln -sf中的-f参数:它会强制覆盖已存在的同名链接,避免因重复操作报错。

4.3 验证链接是否正确

执行:

ls -l S*my*

你应该看到:

S20mydb -> /etc/init.d/mydb.sh S99mytest -> /etc/init.d/mytest.sh

链接创建成功,序号清晰,指向无误。

5. 不用重启,也能验证排序是否生效

等等——难道每次都要reboot才能测?当然不用。那样效率太低,还容易干扰生产环境。

我们用一个更聪明的办法:手动模拟系统启动流程

5.1 按序号顺序手动执行 S 开头的脚本

/etc/rc5.d/目录下,执行:

for script in $(ls S* | sort); do echo "=== Running $script ===" sudo ./$script start 2>/dev/null || echo "[FAIL] $script failed" sleep 0.5 done

这个循环会按字母顺序(也就是按数字顺序)依次执行所有Sxx*脚本。你将实时看到:

  • S20mydb先打印启动日志,2秒后显示 “Database ready.”
  • 然后S99mytest启动,先等几秒,检测到mydb就绪后,才继续自己的启动流程。

5.2 检查日志,确认依赖成立

查看两个日志文件的末尾:

echo "--- mydb.log ---"; tail -n 3 /var/log/mydb.log echo "--- mytest.log ---"; tail -n 5 /var/log/mytest.log

理想输出应类似:

--- mydb.log --- 14:35:10 - [mydb] Starting database mock... 14:35:12 - [mydb] Database ready. --- mytest.log --- 14:35:12 - [mytest] Starting... 14:35:12 - [mytest] Detected mydb is ready. 14:35:13 - [mytest] Started successfully.

看到时间戳对得上:mydb14:35:12就绪,mytest在同一秒就检测到了——说明排序和等待逻辑都工作正常。

你已经用最小成本,验证了整套依赖排序机制。

6. 实战建议:如何给真实服务排好序?

上面是测试,现在回归现实。当你面对 N 个真实服务(Nginx、MySQL、Redis、你的 Python 应用……)时,该怎么动手?

6.1 三步法快速梳理依赖

  1. 列出所有服务及其默认启动序号

    ls -l /etc/rc5.d/S* | awk '{print $9, $11}' | grep -v "\-> /etc/init.d/"

    这会显示当前已启用的所有S脚本及其目标路径,帮你摸清现状。

  2. 画一张依赖图(纸上或白板)

    • 圆圈代表服务(MySQL、Redis、App)
    • 箭头从依赖方指向被依赖方(App → MySQL,App → Redis)
    • 标出你希望的相对顺序(MySQL < Redis < App)
  3. 分配序号,留足余量

    • 不要用满01-99,推荐区间:
      • 基础服务(syslog、network):01-20
      • 中间件(MySQL、Redis、RabbitMQ):21-50
      • 业务应用(Web、API、Worker):51-85
      • 测试/监控/自定义脚本:86-99
    • 每类之间留 5~10 的空档,方便后续插入新服务。

6.2 避坑指南:那些年踩过的排序雷区

  • 雷区1:序号相同导致竞态
    两个S50xxx脚本,系统按字母顺序执行(S50apache2S50mysql前),但你无法保证。永远不要让关键依赖服务共享同一序号。

  • 雷区2:只靠序号,不加运行时检查
    序号只能保证“谁先执行”,不能保证“谁先就绪”。MySQL 脚本可能已启动,但端口还没监听完成。务必在应用脚本中加入健康检查(如nc -z localhost 3306curl -f http://localhost:8080/health)。

  • 雷区3:忽略停止顺序
    启动是Sxx,停止是Kyy。如果你的应用S80app依赖S20mysql,那么停止时,应该让K20appK80mysql之后执行(即K20app数字更小),确保应用先停、数据库后停。chkconfig行第二个数字就是停止序号(如chkconfig: 2345 80 20)。

  • 正解:用update-rc.d(Debian/Ubuntu)或chkconfig(CentOS)管理
    它们会自动处理软链和序号,比手敲ln更安全:

# Ubuntu/Debian sudo update-rc.d mydb defaults 20 80 sudo update-rc.d mytest defaults 99 01 # CentOS sudo chkconfig --add mydb sudo chkconfig mydb on sudo chkconfig mydb priority 20

7. 总结:排序不是玄学,是可验证的工程动作

我们从一个简单的测试脚本出发,一路走到了真实服务的排序实践。回顾一下,你真正掌握的是什么?

  • 不是记住rc5.d这个路径,而是理解“运行级别决定执行目录”这一设计逻辑
  • 不是死记S99代表最后,而是明白“依赖 = 启动序号的严格大小关系”这一本质
  • 不是学会ln -s命令,而是建立了“先建脚本 → 再定序号 → 最后验日志”的闭环验证习惯
  • 不是为了兼容老系统,而是获得一种底层、稳定、不依赖特定工具链的依赖治理能力

在容器和 Kubernetes 时代,rc.d机制看似过时,但它所承载的思想——显式声明依赖、有序执行、可观测验证——从未过时。Docker Compose 的depends_on、K8s 的 Init Container、甚至现代 CI/CD 的 job 依赖,都是这一思想的延伸。

所以,别把它当成历史遗迹。把它当作一把尺子,用来衡量任何依赖方案是否足够清晰、是否经得起验证。

下次再遇到“服务起不来”的告警,别急着翻文档、查日志、重启大法。先问一句:它的启动序号,配得上它的依赖关系吗?


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/4 5:53:43

MedGemma-X效果展示:对‘心胸比>0.5’等量化指标的自动测量与提示

MedGemma-X效果展示&#xff1a;对‘心胸比>0.5’等量化指标的自动测量与提示 1. 这不是CAD&#xff0c;是会“读片”的AI医生 你有没有见过这样的场景&#xff1a;放射科医生盯着一张胸部X光片&#xff0c;用卡尺在屏幕上反复比划&#xff0c;一边数肋骨、一边找心影边界…

作者头像 李华
网站建设 2026/2/5 0:45:57

图片旋转判断开发者案例:基于阿里开源模型构建轻量校正服务

图片旋转判断开发者案例&#xff1a;基于阿里开源模型构建轻量校正服务 你有没有遇到过这样的问题&#xff1a;用户上传的图片歪着、倒着&#xff0c;甚至横着&#xff1f;在OCR识别、证件照审核、电商商品图处理等场景中&#xff0c;一张没对齐的图片可能直接导致后续所有流程…

作者头像 李华
网站建设 2026/2/4 5:35:01

Clawdbot直连Qwen3-32B教程:Web界面支持暗色模式+无障碍访问WCAG标准

Clawdbot直连Qwen3-32B教程&#xff1a;Web界面支持暗色模式无障碍访问WCAG标准 1. 为什么这个配置值得你花5分钟试试 你是不是也遇到过这些情况&#xff1a; 想快速体验Qwen3-32B大模型&#xff0c;但本地部署太重、云服务又贵&#xff1b;打开一个AI聊天页面&#xff0c;眼…

作者头像 李华
网站建设 2026/2/4 21:04:58

VibeVoice-TTS全流程演示:输入文本到播放音频

VibeVoice-TTS全流程演示&#xff1a;输入文本到播放音频 你有没有试过——写好一段四人对话脚本&#xff0c;点下“生成”&#xff0c;90秒后&#xff0c;耳机里就传来自然停顿、情绪分明、音色不串的播客级音频&#xff1f;不是机械朗读&#xff0c;不是拼接剪辑&#xff0c…

作者头像 李华
网站建设 2026/2/5 22:25:23

nmodbus主站跨平台开发:.NET Core应用示例

以下是对您提供的博文内容进行 深度润色与结构重构后的技术文章 。全文已彻底去除AI生成痕迹,采用真实工业开发者口吻撰写,语言自然、逻辑严密、重点突出,并强化了教学性、实战性和平台差异的“人话解读”。文中删减冗余套话,增强段落节奏感,补充关键细节与经验判断,同…

作者头像 李华