Appearance
进程管理
服务出问题时,排查通常从进程是否存在、资源占用是否异常、由谁启动、受什么管理器管开始。
一、进程基础概念
进程是正在运行的程序实例。一个程序可以启动多个进程,每个进程有唯一的 PID(进程 ID)。进程可以继续创建子进程,形成父子关系——排查时不能只看命令名,还要看 PID、PPID(父进程 ID)、运行用户和启动来源。
查看当前 Shell 自己的进程信息:
bash
echo $$ # 当前 Shell 的 PID
ps -p $$ # 查看该进程详情查看进程树(父子关系):
bash
pstree -ppstree 不是默认安装的:
bash
yum install psmisc -y # RHEL 系
apt install psmisc -y # Debian 系进程由谁启动直接决定排查入口。systemd 拉起的进程、Shell 手动启动的进程、supervisor 托管的进程、容器运行时管理的进程——它们的日志位置、重启策略、环境变量都不同。
二、ps 查看进程
bash
ps aux # BSD 风格,显示所有用户的所有进程
ps -ef # Unix 风格,功能类似
ps aux | grep nginx查看指定 PID 的进程,并只显示关心的字段:
bash
ps -p 1234 -o pid,ppid,user,stat,pcpu,pmem,cmd常用输出字段:
| 字段 | 含义 |
|---|---|
| PID | 进程 ID |
| PPID | 父进程 ID |
| USER | 进程的运行用户 |
| STAT | 进程状态(R=运行中、S=休眠、D=不可中断睡眠、Z=僵尸) |
| %CPU | CPU 使用百分比 |
| %MEM | 内存使用百分比 |
| CMD | 启动命令行 |
CMD 列可能被终端宽度截断。要看完整启动命令,直接读 /proc/PID/cmdline:
bash
tr '\0' ' ' </proc/1234/cmdline/proc/PID/cmdline 中的参数以 null 字符分隔,tr 把它们转成空格。
三、top 和 htop
top 提供实时的进程信息刷新:
bash
toptop 界面中的常用交互键:
| 键 | 功能 |
|---|---|
P | 按 CPU 使用率排序 |
M | 按内存使用率排序 |
1 | 展开显示每个 CPU 核心 |
k | kill 指定进程(会提示输入 PID) |
q | 退出 |
htop 是 top 的增强版本,支持鼠标操作、颜色显示和更直观的进程树。不一定默认安装:
bash
yum install htop -y # RHEL 系
apt install htop -y # Debian 系CPU 使用率飙高时,先 top 定位进程,然后看该进程内的线程分布:
bash
top -H -p <pid>Java、Go、Node.js 这类多线程服务排 CPU 瓶颈时,看线程级别比看进程级别更有效。
四、信号和 kill
信号是 Linux 进程间通信的一种方式,最常用的场景是通知进程终止或重新加载配置。
| 信号 | 编号 | 含义 |
|---|---|---|
| SIGTERM | 15 | 请求进程正常退出(可被进程捕获并处理) |
| SIGKILL | 9 | 强制终止(不可被捕获,内核直接终止进程) |
| SIGHUP | 1 | 常用于通知进程重新加载配置 |
bash
kill 1234 # 默认发送 SIGTERM(15)
kill -15 1234
kill -9 1234 # 强制终止SIGTERM 是"请你自己停掉"——进程收到后可以清理临时文件、关闭连接、释放资源再退出。SIGKILL 是内核直接终止进程,进程没有机会做任何清理。如果某个服务经常需要用 kill -9 才能停,说明它的信号处理和退出逻辑可能有问题。
按名称操作进程:
bash
pkill nginx # 向所有名为 nginx 的进程发 SIGTERM
pgrep -a nginx # 先看有哪些匹配的进程pkill 的匹配范围可能比预期大——pkill nginx 可能也匹配到 nginx-exporter 之类的进程。执行前先 pgrep -a 看清楚匹配结果。
五、前台和后台任务
在 Shell 中,命令默认在前台运行,会阻塞 Shell 直到执行完成。可以在命令末尾加 & 放到后台:
bash
sleep 100 &
jobs # 查看当前 Shell 的后台任务前后台切换:
bash
fg %1 # 把编号为 1 的后台任务切回前台
Ctrl+z # 暂停当前前台任务
bg %1 # 继续在后台运行被暂停的任务临时命令用 & 或 Ctrl+z 处理没问题。但长期运行的任务靠 & 管理有隐患:SSH 断开后进程可能被 HUP 信号终止、输出没地方去、进程状态靠记忆追踪。长期任务建议按场景选工具:
| 场景 | 合适的工具 |
|---|---|
| 临时长命令 | tmux / screen |
| 服务进程 | systemd |
| 容器服务 | 容器运行时 / Kubernetes |
| 定时任务 | systemd timer / cron |
六、systemd
systemd 是目前大多数 Linux 发行版的服务管理器,负责在系统启动时按依赖关系拉起服务、监控运行状态、接管日志、在进程异常退出时按策略重启。
systemd 管理的对象叫 unit,常见的 unit 类型:
| 类型 | 用途 |
|---|---|
.service | 定义服务进程 |
.timer | 定时触发,替代 cron 的另一种方式 |
.socket | 基于 socket 激活服务(先监听再按需启动) |
.mount | 管理挂载点 |
.target | 一组 unit 的集合,相当于运行级别的概念 |
查看服务状态:
bash
systemctl status nginx
systemctl is-active nginx # 只看是否在运行
systemctl is-enabled nginx # 只看是否设了开机自启启动和停止:
bash
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx # 重新加载配置(不重启进程)开机自启:
bash
systemctl enable nginx # 设为开机自启
systemctl disable nginx # 取消开机自启查看服务日志:
bash
journalctl -u nginx -f # 持续跟踪
journalctl -u nginx --since "1 hour ago" # 只看最近一小时服务启动失败的排查流程:
bash
systemctl status service-name --no-pager # 看当前状态和最后几行日志
journalctl -u service-name -n 100 --no-pager # 看最近 100 行日志
systemctl cat service-name # 看实际加载到的 unit 内容systemctl cat 比手动找 unit 文件更准确——它会把 /etc/systemd/system/ 下的覆盖配置(drop-in)也合并显示出来。
七、unit 文件
unit 文件定义了 systemd 如何管理一个服务。文件位置:
| 路径 | 用途 |
|---|---|
/usr/lib/systemd/system/(RHEL 系) | 软件包安装的 unit |
/lib/systemd/system/(Debian 系) | 软件包安装的 unit |
/etc/systemd/system/ | 自定义 unit 和覆盖配置 |
一个最小可用的 service 文件:
ini
# /etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/app
Restart=always
RestartSec=5
User=app
Group=app
[Install]
WantedBy=multi-user.target三个段的职责:
| 段 | 作用 |
|---|---|
[Unit] | 服务描述、依赖关系和启动顺序 |
[Service] | 进程如何启动、停止、重启,以什么身份运行 |
[Install] | systemctl enable 时挂在哪个 target 下面 |
常用字段详解:
| 字段 | 含义 | 备注 |
|---|---|---|
Description | 服务说明 | 在 systemctl status 中显示 |
After | 排在哪个服务之后启动 | 只表示顺序,不表示依赖;对方启动失败不会影响自己 |
Requires | 强依赖,对方失败则自己也会失败 | 适合"没网络这个服务没意义"的场景 |
Wants | 弱依赖,对方失败不影响自己 | 适合"对方在最好,没有也能接受" |
Type | 进程启动类型 | 决定了 systemd 怎么判断服务是否"已启动" |
ExecStart | 启动命令 | 写绝对路径,systemd 的 PATH 和登录 Shell 不同 |
Restart | 退出后的重启策略 | always / on-failure / no |
RestartSec | 重启前等待时间 | 防止失败后疯狂重启 |
User / Group | 以哪个用户身份运行 | 服务长期用 root 跑,风险偏高 |
EnvironmentFile | 从这里读取环境变量 | 端口、路径、密钥等配置适合放单独文件 |
WantedBy | enable 时的挂载目标 | 通常用 multi-user.target |
Type 的选择直接影响 systemd 能否正确判断服务状态:
| Type | 适用情况 |
|---|---|
simple | 程序以前台方式运行,启动即就绪(最常见) |
forking | 程序自己 fork 到后台(老式守护进程常用) |
oneshot | 任务执行完就退出,用于初始化脚本 |
notify | 程序主动通过 socket 通知 systemd"我已就绪" |
现在写服务优先让程序以前台方式运行,用 Type=simple。把守护化(daemonize)交给 systemd 管,日志和退出状态更好追踪。
编写和启用的流程:
bash
vim /etc/systemd/system/myapp.service
systemctl daemon-reload # 让 systemd 重新读取 unit 文件
systemctl start myapp
systemctl status myapp --no-pager
systemctl enable myapp # 设置开机自启修改 unit 后同样需要 systemctl daemon-reload 再 restart。
常见启动失败的排查:
| 现象 | 常见原因 |
|---|---|
| 手动执行正常,systemd 启动失败 | 缺少环境变量、工作目录不对、路径不是绝对路径 |
| 服务启动后立刻 failed | 程序自己退出了,或者 Type 写错 |
| 修改 unit 后不生效 | 忘了 systemctl daemon-reload |
| 服务反复重启 | Restart=always 配合程序快速失败 |
| 日志找不到 | 程序把日志写到了文件而不是 stdout/stderr,或者 journald 配置了不持久化 |