我经常在线上问题群里看到这样的描述:“服务器没有挂,但接口时不时超时,重启一下又好一阵子,过几天又开始了。”这种问题往往最让人头痛,因为它既不像宕机那样定位明确,也不像代码报错那样有堆栈可以查。它表现出来的是“时好时坏”,是“偶发超时”,是“重启后短暂恢复”,而这种状态,就是本文要讨论的“不稳定服务器”。
本文是《不稳定服务器的统治》系列第二季。第一季我们重点解决“怎么从零了解一台服务器”,这一季的目标更直接:遇到一台不稳定的服务器,如何按照一套可复用的路径去排查,而不是靠猜和反复重启。内容会覆盖 CPU、内存、磁盘、网络四个核心维度的排查方法,也会补充虚拟化环境、云服务器、容器场景下常见的特殊坑点。
如果你是后端开发、运维工程师、SRE,或者正在自己折腾什么技术项目,这篇文章都值得跟着过一遍。读完你至少能回答三个问题:服务器负载到底看哪个指标?内存被打满之前系统日志里会留下什么?为什么网络明明通,接口还是超时?把这些问题的边界理清,不稳定服务器的统治就会被拉下马。
1. 服务器不稳定到底在说什么
1.1 不稳定不是彻底宕机,而是“时好时坏”
先给“不稳定服务器”下一个实用定义:一台服务器如果偶尔宕机、频繁重启,那是高可用问题;如果从某一刻开始持续性能下降,那是容量或性能瓶颈;而“不稳定”通常是间歇性异常,比如每过几十分钟出现一次超时,或者每天固定时段 CPU 飙升,又或者内存占用只涨不降,直到被杀进程后才恢复。
这种“时好时坏”的状态比彻底宕机更难处理,原因在于它的复现窗口很随机。你登录服务器时可能一切正常,load average 很漂亮,内存也没满,可业务方就是持续反馈“又卡了”。于是很多人陷入一个循环:出了问题先重启,重启后暂时恢复,恢复后没有进一步排查,直到下次再犯。这样下去,不稳定会变成常态,而且每次重启都会抹掉部分现场证据,后续根因分析更难做。
1.2 不稳定服务器的典型表现
从运维和开发的实际经验看,不稳定服务器的典型表现可以归纳为以下几类:
- 平均负载忽高忽低,CPU 使用率有时冲到接近 100%,有时又很低。
- 内存持续增长,最终触发 OOM(Out Of Memory)机制,进程被系统杀掉。
- 磁盘接近写满,或者磁盘 IO 等待时间明显偏高。
- 网络偶发丢包、TCP 重传增多,客户端频繁报连接超时。
- 应用进程本身没有退出,但对外端口不可连接或响应极慢。
- 服务器时间出现偏移,导致日志时间线错乱、定时任务执行异常。
- 容器或虚拟机场景下,宿主机资源竞争导致指标异常。
这些表现往往不是独立的。比如磁盘 IO 被打满之后,业务线程全部卡在等待 IO,从外部看就是接口超时;内存被 OOM killer 干掉后,进程可能自动重启,从外部看就是“服务闪断了一下”。所以排查不稳定服务器时,不能只看一个指标,需要按照固定顺序把所有关键指标过一遍。
1.3 为什么服务器不稳定很难排查
难排查主要有三个原因。第一,很多服务器在上线初期没有配置监控,等到出问题时,现场已经被下一次重启或日志滚动覆盖。第二,排查手段不系统,很多人习惯登录服务器后先看 top,但 top 只能看到当下的瞬时状态,看不到几分钟前的峰值。第三,多个指标同时异常时,很难分辨因果。比如 CPU 高可能是某个业务进程导致,也可能是磁盘 IO 慢导致大量 D 状态进程堆积。
解决这个问题的关键,是建立一套固定的排查顺序,并且理解每个指标之间的关联。下面我们从排查前的准备开始,一步一步把链路打通。
2. 排查前必须准备的三件事
2.1 监控先行:没有指标就没有排查
排查不稳定服务器,最忌讳的就是等事情发生后再登录服务器。如果事前有监控数据,至少能回答几个关键问题:问题从什么时间开始?哪个指标先异常?持续了多久?反之,如果没有任何历史指标,排查基本就是大海捞针。
基础监控至少包括系统负载、CPU、内存、磁盘、网络、关键进程状态和系统日志。生产环境推荐使用 Prometheus 加 node_exporter 加 Grafana 的组合,也可以使用云厂商自带的监控平台。对于个人测试机或临时排查,可以先准备一个手动巡检脚本,快速收集现场信息。
#!/bin/bash # 文件路径:/usr/local/bin/server_check.sh # 服务器稳定性快速巡检脚本,建议配合 cron 每 5 分钟执行一次 echo "===== $(date '+%Y-%m-%d %H:%M:%S') 开始巡检 =====" echo "--- 负载与进程状态 ---" uptime top -b -n 1 | head -20 echo "--- 内存状态 ---" free -h echo "--- 磁盘容量 ---" df -h echo "--- 磁盘 inode ---" df -i echo "--- 系统日志中的 OOM / IO 错误 ---" dmesg -T 2>/dev/null | grep -iE 'oom|killed process|blocked for more than' | tail -20 echo "--- 正在监听的端口 ---" ss -tlnp echo "--- TCP 重传统计 ---" netstat -s | grep -i 'segments retransmited\|retransmited' || true这个脚本里的每条命令都有明确作用。uptime看平均负载,top看进程和 CPU 状态,free -h看内存,df -h和df -i分别看磁盘容量和 inode,dmesg翻系统日志里的 OOM 和 IO 阻塞记录,ss -tlnp看当前监听的端口,netstat -s看 TCP 重传。出现不稳定时先跑一遍,很多问题都能直接暴露。
2.2 时间同步:所有日志的时间线必须一致
排查不稳定服务器时,有一件很容易被忽略但极其重要的事:时间同步。应用日志、系统日志、监控数据分布在多台服务器上,如果各自时间不一致,串时间线时会非常痛苦。常见的表现是:数据库日志显示某条 SQL 在 10:00:00 执行,应用日志却显示 10:03:00 才发起请求,中间这 3 分钟是时间差还是性能问题,根本说不清。
Linux 服务器推荐使用 chrony 替代传统 NTP 服务。配置文件位于/etc/chrony/chrony.conf,最小配置如下:
# /etc/chrony/chrony.conf 示例,server 地址请根据你的网络环境调整 server ntp.aliyun.com iburst server ntp.tencent.com iburst # 允许本地客户端同步自己的时间 local stratum 10 # 启动并设置开机自启 systemctl enable chronyd systemctl restart chronyd # 查看时间同步状态 chronyc sources -v chronyc tracking如果你需要检查校时服务器(NTP 服务器)的 123 端口是否被防火墙关闭,可以尝试用nc -uvz <NTP服务器IP> 123测试 UDP 端口连通性,但 UDP 探测的结果不如 TCP 直观,更可靠的方式是直接在客户端执行chronyc sources -v,看^*标记是否出现。*表示当前已同步源,^表示该源可用。如果一直看不到同步源,再去检查防火墙和网络路由。
还要注意时区问题。如果服务器时区不对,即使时间同步成功,业务看到的日志时间也可能和预期不一致。可以用timedatectl set-timezone Asia/Shanghai设置时区,用timedatectl查看当前系统时间和时区信息。
2.3 建立变更记录:90% 的不稳定来自变更
监控和时间同步都是“事后取证”的基础,但真正能大幅缩小排查范围的,是变更记录。很多人排查到深夜,最后发现是昨天加了一个定时任务,或者升级了某个依赖包。可如果没有变更记录,所有可能性都只能靠猜。
这里说的变更不仅指代码发布,还包括系统内核参数调整、数据库配置修改、中间件版本升级、定时任务新增、防火墙规则变更、云资源规格调整等。建议哪怕是个人服务器,也用一个简单的 Markdown 文件记录每次变更的时间和内容。团队环境则应该借助发布平台或 CI/CD 系统的历史记录,保留每个环境的完整变更历史。这样当服务器开始不稳定时,第一步就可以对比“不稳定开始时间”和“最近变更时间”,如果高度重合,优先回滚或排查变更项,效率会高很多。
3. 第一层排查:CPU 与平均负载
3.1 先看平均负载,而不是直接看 CPU 使用率
很多人习惯用top里的%Cpu判断服务器是否吃紧,但平均负载(load average)是更值得先看的指标。平均负载代表单位时间内正在运行和不可中断的进程数量,它不只反映 CPU 使用,还包含处于 D 状态(不可中断睡眠)的进程。一个典型的场景是:磁盘 IO 出问题后,大量进程阻塞在 IO 等待上,平均负载飙升,但 CPU 使用率并不高。
执行uptime可以看到三组负载值,分别对应过去 1 分钟、5 分钟和 15 分钟的平均值。如果 1 分钟负载远高于 15 分钟负载,说明问题刚刚发生;如果三者都很高,说明问题已经持续一段时间。判断负载是否过高,不能只看绝对值,要结合 CPU 核心数。经验上,当平均负载持续超过核心数的 70% 到 80% 时,就需要开始排查了。比如 4 核服务器,负载持续高于 3 就要警惕。
3.2 定位消耗 CPU 的进程
确认负载过高后,下一步是找出到底是谁在消耗资源。top -c可以在进程列表里显示完整命令行,按大写P可以按照 CPU 使用率排序。为了看到更细粒度的数据,可以配合以下命令:
# 按 CPU 列出前 20 个进程 ps -eo pid,ppid,user,%cpu,%mem,cmd --sort=-%cpu | head -20 # 观察指定进程的 CPU 变化 pidstat -p <PID> 1 5如果消耗 CPU 的是业务进程,需要进一步看是正常的流量增长还是代码死循环。如果是 Java 应用,可以使用top -Hp <PID>查看线程级 CPU 消耗,再用jstack导出线程栈来确定具体代码位置。如果是数据库,常见原因是缺少索引或 SQL 没有命中缓存。如果是突然多出来的陌生进程,则要立刻警惕安全问题,检查是否被入侵或者被恶意程序利用。
3.3 僵尸进程与不可中断进程
进程状态也很关键。ps -eo stat,pid,ppid,cmd | awk '$1 ~ /D|Z/'可以筛选出 D 状态和 Z 状态的进程。D 状态是不可中断睡眠,通常代表进程正在等待内核 IO 完成,大量 D 状态进程说明磁盘或网络 IO 存在严重阻塞。Z 状态是僵尸进程,代表子进程已结束但父进程没有调用 wait 回收,僵尸进程本身不占太多 CPU 和内存,但会占用 PID,大量累积会导致新进程无法创建。
遇到大量 D 状态进程,优先排查磁盘;遇到大量僵尸进程,优先排查父进程是什么,为什么没有正确回收子进程。对于使用 gunicorn、Supervisor 等进程管理工具的场景,还要确认进程管理器的配置是否正常,避免僵死子进程持续堆积。
4. 第二层排查:内存
4.1 free 输出里最容易误解的两个指标
内存排查中有一个高频误区:看到free -h里 used 很高就认为内存不足。实际上 Linux 会尽量利用空闲内存做缓存(cached/buffers),这部分内存在业务需要时可以释放。真正应该关注的是 available 字段,它表示在不触发交换的情况下,可供新进程使用的内存估计值。
运行free -h时,重点看 available 是否持续降低。如果 available 长时间接近 0,同时 swap 使用率上升,说明物理内存已经紧张,系统开始使用交换分区。磁盘交换会带来极大的性能抖动,从外部看就是服务时快时慢,非常符合“不稳定服务器”的特征。
4.2 OOM 发生时,系统的“遗言”在哪里
当内存真正耗尽时,Linux 内核的 OOM killer 会选中一个进程并杀掉它,以释放内存。被杀的进程不一定是内存占用最高的那个,具体的得分逻辑还涉及进程优先级、运行时长等因素,所以会有人遇到“明明另一个进程更占内存,却把 Java 服务杀了”的情况。
OOM 的痕迹不会出现在应用日志里,而会出现在内核日志中。排查命令如下:
# 查看最近的 OOM 记录 dmesg -T | grep -iE 'out of memory|killed process' # 使用 journalctl 查看内核日志 journalctl -k --since "2 hours ago" | grep -iE 'oom|killed process'如果应用进程经常“意外消失”,登录服务器后第一件事就应该是执行这两条命令。看到Killed process 12345 (java)这样的记录,说明不是应用自身崩溃,而是操作系统主动杀掉了进程。根因通常不是“这次被杀”,而是内存为什么持续增长。
4.3 内存泄漏的初步定位思路
内存排查不能只停留在“内存不够就扩容”,很多情况下是程序存在内存泄漏。常见特征:内存使用随运行时间缓慢上涨,重启服务后明显回落,运行几天后又接近峰值。定位时先看哪个进程内存最大:
# 按物理内存占用列出前 10 个进程 ps -eo pid,ppid,user,rss,cmd --sort=-rss | head -10如果是 Java 应用,可以用jmap -heap <PID>查看堆内存概况,也可以借助 Arthas、JProfiler 等工具分析堆转储文件。Python 应用可以结合 tracemalloc 或 objgraph 定位对象引用问题。如果是容器环境,别忘了看 cgroup 层面的限制,容器内即使 free 显示正常,也可能因为容器内存限制被 OOM 杀掉,这时的证据可能在dmesg中显示为Memory cgroup out of memory。
5. 第三层排查:磁盘
5.1 不只是容量,还有 inode
服务器磁盘问题最容易理解,但也有一些隐蔽的坑。第一层是容量,df -h可以直观看到每个分区使用比例,当使用率达到 80% 以上时就要警惕。第二层是 inode,也就是文件系统索引节点数量。如果创建了海量小文件,比如临时文件、缓存文件、消息队列积压文件,磁盘容量可能还没满,inode 就已经耗尽,此时创建任何文件都会提示No space left on device。
用df -i可以查看 inode 使用率。如果 inode 使用率接近 100%,要立刻找出小文件最多的目录。可以通过下面脚本快速定位:
# 统计 /var 下各目录文件数量 for dir in /var /tmp /home /opt; do echo "$dir: $(find $dir -type f 2>/dev/null | wc -l)" done # 查看某个目录的 inode 消耗排行 du --inodes -x /var 2>/dev/null | sort -k1 -n | tail -20注意,找到积压目录后不要贸然批量删除。一定要先确认这些文件属于哪个应用,是否还在被进程写。最安全的做法是先停止相关应用或确认文件无用,再做清理,清理前备份或至少将目录改名保留一段时间。
5.2 IO 延迟高和磁盘阵列异常
磁盘容量只是表面,真正影响稳定性的是 IO 延迟。使用iostat -x 1可以观察每秒的 IO 情况,重点关注%util和await。%util接近 100% 说明磁盘长期处于繁忙状态,await是 IO 请求的平均等待时间,数值越高代表响应越慢。
还有一种情况容易被忽略:磁盘阵列(RAID)中的一块硬盘故障,导致阵列进入降级状态。此时系统还能正常工作,但读写性能大幅下降,而且数据冗余度已经降低,存在数据丢失风险。对于硬件 RAID,建议定时查看阵列管理工具的状态;对于软件 RAID,可以使用以下命令查看:
# 查看软 RAID 状态 cat /proc/mdstat如果阵列状态中出现degraded、removed或[U_]之类的标记,说明有成员盘异常,需要尽快备份数据并更换故障盘。对于生产环境,磁盘阵列状态应该纳入日常监控和告警范围。
5.3 日志无限增长导致磁盘占满
日志是排查问题的重要依据,但日志本身也会成为“罪魁祸首”。很多服务器不稳定,就是/var/log目录被无限制增长的应用日志或容器日志塞满,最终导致磁盘爆掉,服务不断报错。
常见做法是配置 logrotate 对系统日志和应用日志做轮转。容器化环境则要限制 Docker 日志的大小,在/etc/docker/daemon.json中做如下配置:
{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } }这个配置表示单个容器日志文件最大 100MB,最多保留 3 个文件。配置修改后需要重启 Docker 守护进程才能生效,生产环境操作前要评估影响。除了日志,临时目录、上传目录、备份文件也是磁盘增长的常见来源,巡检时需要一并检查。
5.4 共用磁盘的应用互相影响
如果一个磁盘被多个应用共用,那么某个应用的突增写入会拖慢其他应用。比如数据库和 Web 应用放在同一块云盘上,当某条大 SQL 全表扫描导致磁盘 IO 飙高时,Web 应用读取静态文件同样会受影响,外部表现为接口变慢。如果条件允许,建议把日志目录、数据库数据目录、应用临时目录拆分到不同磁盘,从而隔离性能影响。
6. 第四层排查:网络
6.1 网络问题先分层,再定位
网络不稳定的表现很复杂,但排查时可以把问题拆成三层:链路通不通、端口通不通、服务进程响应是否正常。直接从现象出发,先跑几条基础命令:
# 测试到目标服务器的网络连通性 ping -c 5 <服务器IP> # 测试某个端口是否开放 telnet <服务器IP> <端口> # 查看本地监听的端口 ss -tlnp # 看一个接口的完整响应时间 curl -w "总耗时: %{time_total}\n 连接耗时: %{time_connect}\n" -o /dev/null http://127.0.0.1:8080/如果 telnet 能通,但业务接口还是超时,问题大概率在服务进程本身或应用层依赖。如果 ping 不通,问题在链路或防火墙;如果 ping 通、telnet 不通,则要检查端口是否监听、防火墙规则是否允许访问。把问题限制在某个层面后,排查范围会小很多。
6.2 SSH 连接不稳定如何排查
“连接远程服务器总是断”“VSCode 连接 SSH 远程开发一会儿就掉线”是非常典型的不稳定表现。SSH 连接不稳定通常有以下几个原因:
- 网络链路本身有丢包,可以使用
ping -c 100 <IP> | grep loss看丢包率。 - 服务器负载过高或 IO 阻塞,SSH 服务无法及时响应。
- sshd 并发连接配置太低,导致新连接被拒绝。
- 服务器开启了 DNS 反查,但内网 DNS 解析很慢。
- 客户端网络环境切换频繁,连接存活时间太短。
针对 sshd 配置,可以在/etc/ssh/sshd_config中做以下优化:
# /etc/ssh/sshd_config 片段 UseDNS no GSSAPIAuthentication no # 提高并发连接与保持能力 MaxSessions 50 MaxStartups 30:50:100 ClientAliveInterval 60 ClientAliveCountMax 3UseDNS no可以避免 SSH 登录时做反向 DNS 解析;ClientAliveInterval可以让服务端定期发送保活包,减少因网络空闲导致的断连。修改配置后,先用sshd -t检查语法,再执行systemctl reload sshd平滑生效。客户端排查时可以使用ssh -vvv user@host,观察连接卡在哪一步,如果是公钥认证阶段卡住,还要检查本地私钥和服务器authorized_keys的权限设置。
6.3 TCP 重传与连接堆积
网络不稳定还经常表现为 TCP 重传率升高。TCP 协议通过确认机制保证可靠传输,如果数据包在链路中丢失或延迟,发送方会重传。大量重传意味着链路质量差、带宽打满或中间设备出现丢包。
查看重传和连接状态可以使用以下命令:
# 查看 TCP 重传统计 netstat -s | grep -i 'retransmited' # 查看当前连接状态汇总 ss -s # 查看已建立连接数量 ss -tan state established | wc -l # 查看网卡丢包和错误统计 ethtool -S eth0 | grep -iE 'drop|err'如果 established 连接数持续上涨,应用日志却没有明显报错,可能是连接池配置过大,或者有大量慢请求占着连接不放。此时可以结合ss -tan看连接对端 IP 分布,判断是否有异常访问或爬虫流量。生产环境建议对单 IP 并发连接数做限制,同时优化应用的连接池和超时时间。
6.4 DNS 与时间同步也会制造“假不稳定”
还有一种容易被误认为服务器不稳定的是 DNS 解析问题。接口调用依赖外部服务时,如果 DNS 解析经常超时,业务表现为偶发超时。排查时执行time nslookup <域名>,如果解析耗时忽高忽低,说明当前 DNS 服务器不稳定,可以更换为内网稳定的 DNS 或开启本地 DNS 缓存。
时间同步问题同样会表现出“不稳定”特征。比如定时任务在错误时间点启动,导致业务高峰被额外任务抢占资源;或者应用生成的 token 依赖时间戳,时间跳变后出现大量验签失败。前面讲的 chrony 配置就是为此准备的。
7. 虚拟化与云服务器场景
7.1 云服务器的“邻居干扰”
云服务器和物理服务器最大的区别,在于你使用的是宿主机上划分出来的虚拟资源。如果宿主机上的其他实例(业内常说的“邻居”)突发占用大量 CPU、磁盘 IO 或网络带宽,你的实例就会受到干扰。这种问题在共享型实例、免费云服务器或低配轻量应用服务器上更明显。
判断是否被“邻居干扰”,可以看 CPU 的 steal 指标。在top输出中,%st代表当前 CPU 被宿主机强制偷走的时间比例。如果%st长时间较高,说明宿主机资源竞争很激烈。云服务器场景下,如果业务逻辑没有明显变化,但性能突然波动,优先对比监控里的%st和带宽流量。如果确认邻居干扰严重,唯一有效的办法是迁移实例或升级到独享型规格,单纯重启解决不了问题。
7.2 突发 CPU 与带宽基线
不少云服务器是突发型实例,正常情况下允许短时间跑满 CPU,但如果持续满负荷运行,会被限流到基准性能附近。这会带来一种很有迷惑性的现象:刚买来或刚重装系统时性能很好,跑一段时间后突然变慢,重启后又恢复。原因就是重启后 CPU 积分或突发额度被重新计算。
面对这种情况,不要盲目在系统层反复调参,应该先到云控制台查看实例规格、CPU 突发额度和带宽基线。如果业务需要长期稳定运行,应该选择性能型实例,或者对任务进行削峰填谷,避免长时间满跑。
7.3 虚拟机、容器与裸金属的排查层级差异
不同虚拟化技术下,你能看到的系统指标含义并不相同。物理机或裸金属上,free、top、iostat看到的都是真实硬件资源。KVM 虚拟机里,部分指标是宿主机虚拟出来的,CPU 型号和核数通常与实际一致,但性能仍受宿主机调度影响。Docker 容器内执行top和free,看到的是宿主机的内核视角,不一定反映容器真实使用量,更准确的应该使用docker stats查看容器资源占用。
如果容器应用表现为不稳定,优先检查容器是否接近 cgroup 限制,例如内存达到 limit 后会被频繁 OOM,CPU 达到 quota 后会被 throttle。宿主机上的其他容器也可能抢占资源。排查容器问题时,需要同时查看宿主机指标和容器指标,不能只看其中一层。
8. 常见不稳定根因与修复对照表
| 问题现象 | 常见根因 | 快速确认命令 | 处理思路 |
|---|---|---|---|
| 接口偶发超时,CPU 不高 | 宿主机 CPU steal 高或磁盘 IO 阻塞 | uptime/top 看%st,iostat 看%util | 迁移实例,拆分磁盘,排查热点进程 |
| 内存持续上涨后进程被杀 | 应用内存泄漏或容器内存限制过小 | dmesg -T 查 OOM,docker stats 查容器内存 | 修复泄漏,调整 JVM 堆或容器内存 limit |
| 磁盘容量没满但创建文件失败 | inode 被小文件耗尽 | df -i | 清理小文件,调整日志轮转策略 |
| 磁盘写入很慢,服务整体卡顿 | 磁盘 IO 打满或软 RAID 降级 | iostat -x 1,cat /proc/mdstat | 定位写入进程,检查阵列状态,扩容或更换磁盘 |
| SSH 连接经常断开 | 网络丢包、sshd 并发限制、DNS 反查慢 | ping 查丢包,ssh -vvv 看阶段 | 优化 sshd_config,检查客户端网络 |
| 服务偶发无响应,重启后恢复 | 资源耗尽后进程被系统杀掉 | journalctl -k 查 OOM | 按第 3-5 节方法与顺序定位资源瓶颈 |
| 日志时间线和实际不一致 | NTP 未配置或校时端口不通 | chronyc sources -v | 配置 chrony,开放 UDP 123 端口 |
| TCP 重传多,客户端频繁超时 | 链路丢包、带宽打满、防火墙限流 | netstat -s 查重传,ethtool -S 查网卡 | 联系网络服务商,调整 QoS 和限流 |
这个表只覆盖了最常遇到的几个场景。实际生产中,问题可能是多个根因叠加引起的,比如内存泄漏导致服务变慢,服务变慢导致线程堆积,线程堆积导致 CPU 升高,最终整个链路雪崩。所以排查时要保持“先止损、后定位”的思路,而不是试图一次找到所有原因。
9. 生产环境稳定性治理最佳实践
9.1 先止损,再排查
生产环境出现不稳定时,第一目标不是立刻找出根因,而是恢复业务。盲目在一台快挂掉的服务器上执行各种排查命令,不仅危险,还可能加剧问题。合理顺序是:先确认业务影响范围,然后根据情况选择重启单实例、切换流量、扩容新实例或回滚最近变更,待业务稳定后再回头分析根因。
重启有讲究,不建议在同一时间把所有实例全部重启,应该采用滚动重启,每次只重启一部分,观察业务是否恢复再做下一步。如果怀疑是最近发布导致的问题,优先回滚而不是继续调优。止损和排查可以并行,但止损永远拥有更高优先级。
9.2 自动化巡检与告警
服务器不稳定很多情况下是可以提前发现的。以最开始提供的server_check.sh为例,可以把它加入 crontab,定期执行并留档:
# 每 5 分钟执行一次,日志保留在 /var/log 目录 */5 * * * * /usr/local/bin/server_check.sh >> /var/log/server_check.log 2>&1但巡检脚本只能提供快照,真正的稳定性治理需要持续监控和告警。建议至少覆盖以下指标:CPU 使用率、平均负载、内存 available、磁盘使用率、inode 使用率、磁盘 IO 延迟、网络丢包率、TCP 重传率、进程存活状态。监控不是为了在故障后看回放,而是为了在故障前收到告警。对于个人项目,可以先用简单的 shell 脚本加邮件通知;对于团队项目,建议搭建完善的监控告警体系。
9.3 容量规划与架构冗余
稳定性不只是排查问题,更是提前避免问题。容量规划的关键是给关键资源留足余量,通常建议 CPU、内存、磁盘使用率达到 60% 到 70% 时就考虑扩容或清理,而不是等到 90% 再处理。带宽和连接数也要做容量评估,业务增长时及时扩容。
架构层面,尽量消除单点。使用负载均衡对外提供服务,数据库做主从或高可用集群,缓存和消息队列做集群部署。需要考虑的一个问题是:引入集群后虽然提升了可用性,但也引入了新的不稳定因素,比如集群节点时钟不同步、脑裂、配置不一致等。架构越复杂,对监控和变更管理的要求就越高。
对于 Web 服务器安全,不要只关注性能。定期更新系统补丁,关闭不必要的服务端口,限制 SSH root 远程登录,使用密钥认证代替密码登录,配置防火墙最小开放原则,这些都属于稳定性的基础工程。安全事件是造成服务器不稳定的一个非常常见的诱因,被植入挖矿程序后,CPU 经常飙到 100%,这种不稳定不是资源规划能解决的。
GPU 服务器是另一个需要单独关注的方向。训练任务表现不稳定时,除了常规的 CPU、内存、磁盘,还要关注 GPU 温度和显存占用。执行nvidia-smi可以看到 GPU 利用率、显存、温度、风扇转速,以及 ECC 错误计数。如果 ECC 错误持续增长,说明 GPU 硬件可能存在问题。多卡训练还要检查 NVLink 或 PCIe 链路状态。GPU 服务器的运维重点在于驱动版本、CUDA 版本、显存清理和温度控制,这些内容值得单独写一篇文章。
9.4 变更管理与可观测性
最后强调两点:第一,所有变更必须可回溯;第二,所有关键路径必须可观测。可回溯意味着你能回答“这台服务器的配置昨天是什么样,今天改了什么,为什么改”。可观测意味着当链路某个环节变慢时,你能快速判断是应用问题、数据库问题还是基础设施问题。
对个人开发者来说,即使没有团队,也建议养成写变更日志的习惯。很多“莫名其妙”的不稳定,在记录变更之后都会变得“有迹可循”。对团队来说,则要建设好日志聚合、链路追踪和监控告警三条核心能力。这类基础设施建设看起来投入大,但每一次故障排查中节省的时间,都会成倍回报回来。
排查不稳定服务器的方法到这里已经完整串联起来了:从准备监控和时间同步开始,依次检查 CPU 和负载、内存与 OOM、磁盘容量与 IO、网络与连接,再结合虚拟化和云服务器的特殊场景,最后用变更记录和容量规划来防止问题复现。与其收藏这篇文章,不如先把文中那个简易巡检脚本放到服务器上跑一次,等下次服务器再次露出“不稳定的统治”苗头时,你至少已经有了可以按顺序出牌的底牌。