接手一台新服务器,我第一件事通常不是装环境,而是先跑一遍 ps aux 看看系统里到底跑了些什么。很多问题——卡顿、端口冲突、CPU 飙高、莫名其妙的服务重启——根子就藏在进程列表里。而“计划任务”则是另一个容易被忽视的坑:脚本明明写了定时执行,结果就是没跑,或者跑了一次之后再也不跑,翻日志才发现是环境变量、权限、时区这些细节在作祟。这篇就把进程管理和计划任务管理这两块一次讲透,重点放在实际运维里怎么用、怎么排查、怎么避坑。
内容主要分六大块:先讲进程怎么查、怎么看状态;再讲进程优先级和调度,这是很多人忽略的隐性规则;第三块是计划任务的四种主流实现方式,cron、at、anacron、systemd timer 怎么选;第四块是异常进程排查实战,CPU 100%、僵尸进程、内存泄漏、进程杀不掉,每个都给出可落地的排查路径;第五块围绕“怎么让进程稳定地在后台跑”展开,nohup、setsid、systemd、Docker 重启策略逐一对比;最后把我在生产环境里踩过的坑集中整理成速查表。文中既有命令示例也有排查思路,适合 Linux 运维初学者系统学习,也适合有一定基础的朋友查漏补缺。
1. 先把家底摸清楚:进程查看与状态解读
1.1 ps、pstree、pgrep:三件套的用法和侧重点
排查进程问题,最常用的就是 ps。但很多人只会 ps aux,然后盯着输出发呆。这里给你一个实际工作中效率最高的组合:
bash复制# 查看所有进程,显示完整命令行
ps -ef
# 查看所有进程,带CPU和内存占用百分比
ps aux
# 按CPU占用排序,取前10
ps aux --sort=-%cpu | head -10
# 按内存占用排序,取前10
ps aux --sort=-%mem | head -10
# 查看某个进程的详细环境
ps -e -o pid,ppid,stat,comm,args,user --forest
ps -ef 和 ps aux 的区别在于输出列。ps -ef 列更少、更清爽,适合看进程父子关系;ps aux 多了 CPU、内存、运行时间,适合找“谁在吃资源”。我个人的习惯是先用 ps aux --sort=-%cpu 找异常,确认 PID 之后再用 ps -ef --forest 看它的父亲是谁,追溯启动来源。
pstree 命令能把进程树画出来,相比 ps --forest 更直观。排查“这个进程是谁拉起来的”非常有用:
bash复制# 显示完整进程树
pstree -p
# 只看某个PID的进程树
pstree -p 2345
pgrep 适合写脚本时用,直接按名字找 PID,比 ps aux | grep xxx | grep -v grep 这种写法优雅得多:
bash复制# 按进程名找PID
pgrep -l nginx
# 按完整命令行匹配
pgrep -f "java -jar app.jar"
# 按用户过滤
pgrep -u www-data -l php
1.2 进程状态 STAT 一栏到底在说什么
ps aux 输出的 STAT 列,很多人扫一眼就过了,其实这里信息量极大。常见的状态字符含义如下:
| 状态 | 含义 | 常见场景 |
|---|---|---|
| R | Running/Runnable,正在运行或可运行 | CPU 密集任务 |
| S | Sleeping,可中断睡眠 | 绝大多数等待 IO、等待网络、sleep 的进程 |
| D | Uninterruptible Sleep,不可中断睡眠 | 等待磁盘 IO,卡 NFS、卡硬件时会看到 |
| Z | Zombie,僵尸进程 | 子进程已结束但父进程未调用 wait 回收 |
| T | Stopped,被暂停 | Ctrl+Z 挂起,或 kill -STOP |
| I | Idle,内核线程空闲态 | 内核线程常见 |
| t | 被调试器跟踪暂停 | gdb/strace 附加时可见 |
| s | 会话首进程 | 通常是 session leader,比如 bash 会话 |
| + | 前台进程组 | 当前终端的前台任务 |
| l | 多线程进程 | 每个线程单独显示时可见 |
这里面最需要警惕的是 D 状态和 Z 状态。D 状态表示进程卡在内核态等待某个 IO 完成,最大的特点是——kill -9 杀不掉。生产环境里最常见的触发场景是 NFS 挂载点失联、磁盘硬件故障、或者某些驱动卡死。遇到 D 状态进程,先别急着杀,strace -p PID 看看卡在哪个系统调用上,同时用 iostat、dmesg 确认磁盘和网络存储的状态,否则就算重启进程也还是会继续卡。
Z 状态就是僵尸进程,表示子进程已经死了,但父进程没有调用 wait() 回收它的进程描述符。少量僵尸不用慌,但僵尸数量持续增长就要查父进程了。具体排查方法后面第 4 部分详细说。
1.3 top 和 htop:动态监控的正确打开方式
静态快照用 ps,动态监控就要上 top。top 打开后,几个操作必须熟:
bash复制# 直接按 CPU 排序启动
top -o %CPU
# 按内存排序
top -o %MEM
# 查看指定 PID 的线程,常用于排查 Java/Python 程序的线程问题
top -H -p 2345
# 指定刷新间隔,0.5秒一次
top -d 0.5
top 交互界面里,按 1 查看每个 CPU 核的负载,按 P 按 CPU 排序,按 M 按内存排序,按 c 显示完整命令行,按 x 高亮当前排序列。这些都是基础但极其实用的操作。
htop 是 top 的增强版,彩色界面、支持滚动、能用鼠标操作、F5 看树状结构。没有的话安装也很简单,CentOS/RHEL 用 yum install htop,Ubuntu/Debian 用 apt install htop。我个人在排查线程级问题时,更喜欢用 htop 配合 H 键把线程展开,能直接看到哪个线程把 CPU 打满,然后去代码里定位。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. CPU 资源分配规则:进程优先级与状态流转
2.1 nice 值:Linux 调度器给进程的“插队权重”
Linux 的完全公平调度器(CFS)里,每个进程都有一个 nice 值,范围是 -20 到 19。数值越小,优先级越高。普通用户只能把 nice 调大(降低优先级),只有 root 能调小(提高优先级)。
启动时指定优先级:
bash复制# 以较低优先级运行,nice 值设为 10
nice -n 10 ./backup.sh
# 以较高优先级运行,只有 root 可以
nice -n -5 ./important_service.py
对已运行的进程调整优先级用 renice:
bash复制# 把 PID 2345 的 nice 值调整为 5
renice -n 5 -p 2345
# 把某个用户的所有进程 nice 值统一调高
renice -n 10 -u devops
实际使用场景很简单:跑批处理任务、大文件压缩、日志分析脚本时,主动把 nice 调高,避免抢了线上服务的 CPU;而数据库、负载均衡这类核心服务,可以主动调低 nice 值,保证它们在 CPU 竞争中获得更充足的调度份额。注意,renice 调整后 ps 输出里的 NI 列会对应变化,确认结果用 ps -o pid,nice,comm -p PID。
2.2 进程状态流转:从创建到回收的完整生命周期
理解进程状态不能只看零散字母,要从生命周期维度串起来。进程创建用 fork() 复制父进程,子进程继承大部分属性;然后用 exec() 把新程序加载进进程空间;运行期间在 R、S、D、T 之间切换;运行结束调用 exit(),内核保留进程描述符等待父进程 wait() 回收;父进程不回收,就成了僵尸;父进程先死了,子进程被 PID 1(systemd/init)收养,成为孤儿进程。
这里有两个面试常考、日常也常踩的点:
第一,孤儿进程不代表需要人工清理。父进程挂了,systemd 会自动接管,子进程继续运行,最终正常退出。这个机制本身没问题,问题在于父进程意外退出后,子进程往往失去了原生的资源管理逻辑,可能出现资源句柄不释放的情况,所以线上服务一般建议用 systemd 或守护进程管理器来维护,而不是裸起进程。
第二,僵尸进程的回收完全取决于父进程。内核已经释放了僵尸进程占用的内存和文件句柄,只保留了一个 pid、退出状态、资源统计等少量信息。只要父进程不回收,僵尸就一直在。日常处理思路第 4 部分细说。
3. 定时任务四选一:cron、at、anacron、systemd timer
3.1 cron:最常用但不等于“零配置”
crontab 的基本用法不复杂,但坑很多。先列格式:
bash复制# 分 时 日 月 周 命令
* * * * * /path/to/command
配置方法有三种入口:
crontab -e:编辑当前用户的任务,存到 /var/spool/cron/用户名,这种方式推荐优先用,因为自带语法保存检查,出错会拦截。/etc/crontab:系统级 crontab,多了一个“执行用户”字段,例如0 2 * * * root /usr/local/bin/clean.sh。/etc/cron.d/:把不同应用的任务拆成独立文件,适合程序包安装时自带的定时任务,例如安装包写一个/etc/cron.d/myapp,内容同样要带用户字段。
常见表达式直接给几个:
bash复制# 每天凌晨 2 点 30 分执行
30 2 * * * /usr/local/bin/backup.sh
# 每 5 分钟执行一次
*/5 * * * * /usr/local/bin/check_status.sh
# 每周一凌晨 3 点执行
0 3 * * 1 /usr/local/bin/weekly_report.sh
# 每月 1 号和 15 号的凌晨 4 点执行
0 4 1,15 * * /usr/local/bin/monthly_clean.sh
# 工作日上午 9 点到 12 点,每 10 分钟执行一次
*/10 9-12 * * 1-5 /usr/local/bin/workday_check.sh
cron 配置完必须确认 crond 服务是活着的:
bash复制systemctl status crond
然后验证任务是否如期运行,看日志:
bash复制# CentOS/RHEL 系统
tail -f /var/log/cron
# Ubuntu/Debian 系统,需要先开启 cron 日志
grep CRON /var/log/syslog
实际生产里 cron 任务不触发,90% 都不是 cron 本身的问题,而是脚本执行环境差异。cron 执行脚本时的 PATH 极其精简,通常只有 /usr/bin:/bin,你脚本里写 python3、docker-compose、node,如果这些命令在 /usr/local/bin 下,cron 环境里根本找不到。还有个经典坑:在 crontab 里写带 % 的命令,比如 date +%Y%m%d,必须写成 date +\%Y\%m\%d,否则 % 会被 cron 当成换行符处理,命令直接截断。
3.2 at:一次性任务用这个,别硬塞 cron
cron 适合循环任务,但“今天下午 3 点跑一次这个脚本”这种一次性需求用 cron 就很别扭。at 命令就是干这个的:
bash复制# 安装
yum install at -y
systemctl start atd
# 今天下午 3 点执行
echo "/usr/local/bin/script.sh" | at 15:00
# 30 分钟后执行
echo "/usr/local/bin/script.sh" | at now + 30 minutes
# 明天上午 10 点执行
echo "/usr/local/bin/script.sh" | at 10:00 tomorrow
# 查看待执行任务
atq
# 删除任务
atrm 任务编号
at 任务的输出默认会以邮件形式发给用户,服务器没配邮件系统的话,输出会丢到 /var/spool/mail/ 或者直接丢失。所以写 at 任务时,一定要手动把输出重定向到日志文件里,否则脚本报错你完全不知道。
3.3 anacron:解决“关机错过执行”的补偿机制
cron 在系统关机时不会补执行漏跑的任务。比如你设置了每天凌晨 2 点备份,那几天服务器没开机,开机后 cron 不会回头补跑。anacron 就是解决这个问题的:它记录每个任务上次执行的时间,开机后如果发现任务超过周期没跑,会补执行一次。
RHEL/CentOS 里 cron 本身就内置了 anacron 的调度逻辑,/etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthly 目录里的脚本,其实是由 anacron 负责补跑的。所以你的定时脚本如果不是要求“绝对精确到分钟”,直接丢进 /etc/cron.daily 反而更省心——写脚本时加上可执行权限,放在目录里不动了,系统每天会统一调度,漏跑会自动补。但注意精度:cron 精确到分钟,anacron 粒度只有天/周/月。
3.4 systemd timer:现代系统的首选方案
我现在的习惯是,新部署的服务一律用 systemd timer 代替 cron,原因有三个:日志统一走 journald,好查;支持错过补跑(Persistent=true);能配置随机延迟,避免多台服务器同时执行任务造成负载尖峰。
先写 service 单元,定义要执行什么:
ini复制# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=root
再写 timer 单元,定义什么时候执行:
ini复制# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3am
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300
AccuracySec=1min
[Install]
WantedBy=timers.target
启用并验证:
bash复制systemctl daemon-reload
systemctl enable --now backup.timer
systemctl list-timers
systemctl status backup.timer
关键参数说明:
| 参数 | 作用 |
|---|---|
| OnCalendar | 时间表达式,语法比 cron 更丰富,支持 *-*-* 03:00:00、Mon..Fri 09:00:00 等 |
| Persistent=true | 类似 anacron,开机后补跑上次错过的任务 |
| RandomizedDelaySec | 在指定秒数内随机延迟执行,默认无,适合错峰 |
| AccuracySec | 调度精度,默认 1 分钟,设 1 秒用于高精度场景 |
systemd timer 有个隐藏优势:如果任务本身执行时间很长,比如备份跑了 4 小时,cron 会在下次触发时再起一个新实例,两个实例可能互相踩踏;而 systemd 的 service 单元可以用 Lock= 或者 Restart=no 天然避免并发,加上 StartLimitIntervalSec 的配置,异常重试也能控得住。
4. 异常进程排查实战:从发现到根因
4.1 CPU 飙到 100%:定位线程再到代码
这个场景几乎每个运维都遇到过。排查路径按顺序来:
第一步,确认谁在吃 CPU:
bash复制top -o %CPU
记下 PID。比如看到 PID 2345 的 Java 进程 CPU 一直在 300% 左右。
第二步,如果是多线程进程,定位到具体线程:
bash复制# 显示进程内所有线程的 CPU 占用
top -H -p 2345
找到 CPU 高的线程 TID,转成十六进制:
bash复制printf "%x\n" 线程TID
第三步,如果你面对的是 Java 程序,用 jstack 输出线程栈:
bash复制jstack PID | grep -A 30 "nid=0x对应十六进制"
就能看到这个线程停在哪个类哪个方法。不是 Java 的话,可以用 strace -p PID -f 跟踪系统调用,或者 perf top -p PID 看内核层面的热点函数。原生程序也可以用 gdb attach PID 附加看调用栈,但生产环境要小心,gdb attach 会导致进程短暂暂停,数据库之类的核心服务最好先申请窗口。
一个真实案例:某个前端服务偶发请求超时,CPU 并不高,但某线程一直处于 R 状态空闲转圈。用 jstack 拉线程栈一看,线程卡在 SocketInputStream.read 上,典型的连接池配置问题——连接被耗尽,线程全部阻塞在等待空闲连接上。这种问题从系统指标上很难看出来,线程栈是唯一的突破口。
4.2 僵尸进程处理:不是杀僵尸,是救父进程
僵尸进程的正确处理方式,不是 kill -9 僵尸PID——没用,僵尸已经死了,信号杀不动。正确的思路是:杀掉僵尸进程的父进程,让僵尸被 systemd 收养后回收。
bash复制# 找到僵尸进程和它的父进程
ps -eo pid,ppid,stat,comm | grep Z
# 再看父进程是什么
ps -ef | grep 父PID
如果父进程是业务进程,评估后重启它,僵尸会随之清理。如果父进程没了(PID 变成 1),僵尸就被 systemd 收养,这种情况一般是父进程异常退出且没有等子进程回收,产生了“隔代遗留”。少数极端情况,僵尸的父进程已经变成 PID 1 但僵尸还在,可以在确认系统没有持有它的文件描述符后先试试 kill -9 $(pgrep -P 1 -l | grep defunct) 思路,但绝大多数场景不需要这么激进。
重点在于:僵尸是表象,根因在父进程没有正常回收子进程。常见根因有三个:父进程代码里漏写 wait();父进程本身被逻辑卡死,无限循环不退出;父进程捕获了 SIGCHLD 信号但没有正确处理。如果频繁出现僵尸,真得让开发看代码,运维这边只能暂时重启父进程止血。
4.3 进程杀不掉:D 状态、权限、内核线程
kill -9 PID 杀不掉,先别慌,按顺序排查三种可能:
第一,D 状态。前面说过,不可中断睡眠只能等内核 IO 返回,kill -9 的信号都被挂起。用 ps aux | grep PID 确认 STAT 是不是 D。这种要处理根因——NFS 挂了就去恢复网络存储,磁盘卡死就联系机房/云厂商排查硬件,业务能接受的话,重启机器通常是最快的恢复手段。
第二,权限不足。普通用户 kill 别的用户进程会被拒,这时 sudo kill 或者切 root。还有一种情况是进程本身处于不可杀掉的内核线程,比如 kworker、ksoftirqd,这些是内核的一部分,PID 1 的子孙,没有对应业务,杀不掉是正常的,别折腾。
第三,进程进入了 D 状态但文件系统卡住。ps 看进程状态是 D,cat /proc/PID/status 最后一列能看到进程在等哪个内核函数。这种情况先把对应的挂载点 umount 或者恢复远端存储,进程通常会自然恢复。
4.4 f 盘被另一个进程锁定:lsof 和 fuser 的正确用法
热词里有个“f盘被另一个进程锁定”,对应到 Linux 场景就是“设备或文件被进程占用”。处理方式很固定:
bash复制# 查看哪些进程打开了某个文件或目录
lsof /data
# 查看哪些进程打开了某个挂载点
lsof +D /data
# 直接找到打开某个文件系统的所有进程
fuser -v /data
# 把占用文件的进程全部杀掉(谨慎用)
fuser -km /data
lsof 的核心价值是看 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME,对照系统挂载情况就能判断哪个进程在持有一个已卸载的目录。常见场景:有人想 umount 一个磁盘,提示 target is busy,先 lsof +D /挂载点 找出占用者,然后决定是 kill 还是通知业务方退出。
5. 进程稳定后台运行:nohup 之外的正确姿势
5.1 nohup、setsid、disown:临时方案对比
很多人知道 nohup,但你问他 nohup 和 setsid 的区别,他会懵。直接给结论:
| 方案 | 原理 | 适用场景 |
|---|---|---|
nohup cmd & |
忽略 SIGHUP 信号,终端关闭后进程继续运行 | 临时在 SSH 会话里起个脚本 |
cmd & disown |
把任务移出 shell 的任务表,shell 退出时不发送 HUP | 临时后台任务,适合 Bash |
setsid cmd |
让进程成为新的会话首进程,彻底脱离终端 | 需要完全独立会话的场景 |
实操示例:
bash复制# nohup 标准写法,日志和错误分别输出
nohup java -jar app.jar > app.log 2>&1 &
# setsid 写法,整个会话都独立
setsid java -jar app.jar > app.log 2>&1 &
# disown 用法,先挂后台再移出任务表
java -jar app.jar &
disown
但这些方案共同的问题:进程如果自己崩了,没有东西把它拉起来。所以只适合临时任务,不适合生产服务。
5.2 systemd service:让进程“死了自动拉起来”
生产环境跑服务,首选就是让 systemd 管理。写一个 service 单元,核心配置如下:
ini复制# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Service
After=network.target mysql.service
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/start.sh
Restart=always
RestartSec=3
StartLimitBurst=5
StartLimitIntervalSec=30
[Install]
WantedBy=multi-user.target
几个关键点要说透:
Restart=always 表示无论退出原因是什么,都重启。RestartSec=3 表示崩溃后等 3 秒再启,防止快速崩溃导致 CPU 被打满。StartLimitIntervalSec=30 和 StartLimitBurst=5 组合表示 30 秒内最多重启 5 次,超过则放弃,并触发 failed 状态,这样能避免“启动就挂、重启又失败”的无限循环。
配置好后:
bash复制systemctl daemon-reload
systemctl enable --now myapp
systemctl status myapp
还有两个容易被忽略的配置。KillSignal=SIGTERM 加上 TimeoutStopSec=30,重启前先给进程一个优雅退出的机会,等 30 秒还退不了再强制 SIGKILL。WatchdogSec=30 配合程序内的心跳,如果程序卡死没及时喂狗,systemd 会主动重启它,这个对“假死”场景特别有用,但需要程序主动支持,不是所有进程都能用。
5.3 Docker 场景下的守护:restart policy 怎么配
容器里跑任务同样需要保证存活。Docker 的 restart policy 有四个级别:
yaml复制version: "3"
services:
app:
image: myapp:latest
restart: unless-stopped
stop_grace_period: 30s
no:不自动重启。on-failure:非零退出码才重启,适合脚本型任务,正常退出别拉起来。always:任何退出都重启。unless-stopped:手动 stop 不重启,其他情况重启。这是长期跑的服务最推荐的一个。
docker compose 里还建议加 stop_grace_period: 30s,等于 systemd 的 TimeoutStopSec,让容器收到 SIGTERM 后有足够时间处理清理逻辑。容器的退出信号默认是 SIGTERM,如果你的程序需要处理 SIGTERM 来做优雅下线,务必在代码里注册信号处理器,否则只能等超时后 SIGKILL。
6. 计划任务与进程排查避坑速查
6.1 计划任务为什么“没执行”:自查清单
我做了个排查顺序表,按优先级从上往下查:
| 检查项 | 命令/方法 | 说明 |
|---|---|---|
| crond 是否在跑 | systemctl status crond |
服务没起来一切白搭 |
| 任务是否被正确加载 | crontab -l |
确认配置确实存在 |
| 语法是否合法 | crontab -e 时是否有报错 |
保存时语法错误会拒绝写入 |
| 日志有没有执行记录 | 查 /var/log/cron 或 syslog | 没记录说明调度层就没触发 |
| 环境变量是否缺失 | 脚本里用绝对路径,或手动 source 环境 | 最常见的隐性坑 |
| 时间/时区是否正常 | date -R |
服务器时区错了,任务执行时间就偏移 |
| % 是否被转义 | crontab 里写 \% |
写在命令行里的 % 必须转义 |
| 脚本是否有执行权限 | ls -l 脚本 |
权限不足脚本根本跑不起来 |
| 任务执行但报错 | 先手动跑一遍脚本看输出 | 手动能跑、定时跑不了,多半是环境差异 |
6.2 进程排查高频命令速查
最后把进程排查的高频命令整理成一张表。这张表我建议直接收藏,出问题照着敲:
| 目标 | 命令 |
|---|---|
| 看所有进程快照 | ps -ef / ps aux |
| 动态监控按 CPU 排序 | top -o %CPU |
| 线程级定位 | top -H -p PID |
| 找进程对应文件/端口 | lsof -p PID / lsof -i:8080 |
| 找文件被哪个进程占用 | lsof /path / fuser -v /path |
| 找进程的启动目录和命令行 | ls -l /proc/PID/cwd / cat /proc/PID/cmdline |
| 看进程环境变量 | cat /proc/PID/environ |
| 看进程资源使用 | cat /proc/PID/status |
| 跟踪系统调用 | strace -p PID -f -o trace.log |
| 动态性能采样 | perf top -p PID |
这里特别注意 /proc/PID/ 这个虚拟目录,它包含了进程几乎所有的运行信息。/proc/PID/cwd 是进程当前工作目录,/proc/PID/root 是进程视角的根目录,/proc/PID/fd/ 是进程打开的所有文件描述符。以前排查“nginx 日志文件被删了但磁盘空间没释放”,靠 lsof | grep deleted 找到对应 PID,然后确认是不是需要重启进程释放句柄,这些都是非常实用的招。
6.3 我个人的几条建议
说句实在话,进程和计划任务管理,工具学起来一天就够了,真正的功夫全在习惯上。我建议你从今天开始做三件事:第一,新部署的服务一律写 systemd service 和 timer,不要再用裸 nohup 加 crontab 的方式;第二,所有计划任务的脚本,第一行用绝对路径定义环境变量,脚本内所有命令都用绝对路径,输出日志一定要定向到文件,并且做好日志滚动;第三,每周固定跑一遍 ps aux --sort=-%cpu | head -20 和 systemctl list-timers,很多问题在刚冒头的时候一眼就能发现,真等报警响了再查就晚了。
进程管理说到底就是三件事:知道系统里有什么在跑,知道每个进程在等什么,知道进程挂了能不能自动拉起来。计划任务管理也一样,能查日志、能补跑、能防并发,基本就立于不败之地。这套方法论我用了好多年,踩过脚本半夜跑崩的坑,也排查过僵尸进程堆积的事故,把这些经验沉淀下来,就是上面这些内容了。下次你再遇到进程或者定时任务相关的问题,按这个顺序查,大概率比你在网上乱搜一圈要快得多。
