1. 先别急着刷新:把"网站打不开"拆成五类场景
我得先说实话:绝大多数"网站打不开"的求助,最后查出来的根因都不是什么惊天大坑,反而越是简单的故障,越容易被紧张的排障过程搞复杂。凌晨两点收到告警,群里已经炸成一锅粥,这时候最忌讳的就是手忙脚乱地刷新页面、重启服务器、连看三遍日志——顺序全错了,问题当然查不到。
我自己的习惯是,接到报障后的第一件事不是动手,而是先把问题分门别类。判断"网站打不开"具体属于哪一类,决定了后面排查的方向和对故障等级的预估。根据我这些年处理过的线上故障,基本可以归纳成五类:
| 场景类型 | 典型表现 | 最可能的根因方向 |
|---|---|---|
| 完全打不开 | 浏览器直接提示无法访问、超时 | DNS、链路、防火墙、服务器宕机 |
| 能打开但白屏/卡住 | 页面加载一半不动,或HTTP返回502/504 | 应用进程、数据库、代码异常 |
| 部分用户打不开 | 有的地区正常,有的地区异常 | CDN、DNS调度、多线路机房 |
| 突然发生 | 昨天还好好的,今天凌晨全挂 | 变更窗口、夜间任务、证书过期 |
| 间歇性故障 | 时好时坏,过一会儿自己恢复 | 负载高、连接数满、限流 |
1.1 想清楚这三个问题,比跑命令更值钱
面对每一类故障,我建议你强迫自己在纸上(或者脑子里)先回答三件事:
- 故障是从什么时候开始的?这个时间点之前有没有做过发布、改配置、调参数?
- 影响范围有多大?是所有用户全挂,还是只有某些网络/地域的用户受影响?
- 故障是持续性的,还是脉冲式的?持续性问题多半和配置、进程、资源耗尽相关,间歇性问题多半和负载、并发、锁竞争相关。
这三个问题问完,你大概能划掉一半的错误方向。比如,如果是某个省份的电信用户打不开,而你人在联通机房,那大概率是链路互联互通的问题,这时候你在服务器上折腾半天毫无意义。如果是昨天发版之后出现的间歇性502,那重点应该放在新代码的数据库连接池配置上,而不是去重启PHP进程碰运气。
1.2 变更窗口:排查最快的一条捷径
我在团队里经常反复强调一句话:90%的线上故障都来自变化,不是被攻击了,也不是机器老了,而是某个"看起来人畜无害"的小改动。可能是今早运维顺手改了一下Nginx的超时参数,也可能是开发同学发布时漏了一个依赖包。所以,每次排障前,务必先确认变更窗口。
怎么确认?最直接的是看发布系统/工单系统里的记录。小团队没有自动化发布平台的,至少要有git提交记录和服务器操作历史。登录服务器先跑一条ls -lt /usr/local/nginx/conf/,看配置文件有没有在故障时间点被改过;再翻一下应用的部署目录,看Release目录的时间戳。这一步往往能直接把排查时间从两小时压缩到十分钟。
2. 由外到内快速定界:DNS、链路、服务器一台台过
分类分完了,变更也确认了,下面进入真正的排查阶段。我习惯按"由外到内"的顺序走:客户端 → DNS → 链路 → 服务器 → 应用 → 数据库。每一层只做最少的验证,发现正常就迅速下沉,不要在一个层级上恋战。
2.1 DNS解析:先确认域名是否"指路正确"
很多新手一上来就去登录服务器看日志,结果问题根本不在服务器上。域名解析错了、解析没生效,用户压根就到不了你的服务器,服务器上当然一切正常。
排查DNS最快的方式是用系统自带的工具。Linux/Mac下用dig,Windows老版本用nslookup:
dig example.com A +short nslookup example.com重点看返回的IP对不对,是不是你CDN或源站的真实IP。如果域名明明指向旧机房IP,而你的业务早迁走了,那问题就是DNS记录没更新,或者TTL太高导致客户端的本地缓存还在用旧IP。
还有一种容易忽略的情况:域名被解析到了多个IP,其中一个IP对应的服务器已经下线或者防火墙放行异常。这时候从你本机解析可能一切正常,但某个地区的用户落在了那个坏IP上,表现就是"部分用户打不开"。处理方式是先拿dig查全部的A记录,再看每个IP的健康状态。
2.2 链路探测与本地排除
DNS没问题,接着验证从你当前网络到目标服务器的链路是否通。三条命令足够:
ping -c 5 你的服务器IP telnet 你的服务器IP 80 curl -I https://你的域名ping看网络层的通断和丢包率,telnet验证TCP端口是否开放,curl则直接模拟一次真实的HTTP请求。如果ping通但telnet不通,多半是防火墙或服务没起来;如果ping超时但其他机器访问正常,说明你的出口线路到这台机器有故障,或者服务器的安全组/防火墙把你这个IP段给拦了。
我遇到的比较经典的坑是:服务器在阿里云,安全组规则里放行了80和443,但客户是从某个内部办公网络访问,出口做了端口限制,所有8443以上的端口都被封。这种情况是换一台测试机、或者让用户用手机4G/5G流量访问一下,基本就能立刻定位。
2.3 用第三方视角判断影响范围
单靠你本地的一台电脑,永远只能代表一种网络环境。判断"是全局故障还是局部故障",最快的方式是找第三方监测站点,或者直接问几个不同网络环境的同事。我比较常用的思路:
- 用线上拨测工具(比如听云、博瑞、公开的Ping检测站点)从多个城市发起探测,看是否有大面积超时。
- 如果手上有多地运维能力的同学,在群里喊一声,让北京、上海、广州的同事各ping一次。
- 手机切到4G/5G网络访问,对比WiFi下的表现。
如果拨测结果全国都超时,那基本可以确认是源站或CDN域名调度层面出了问题;如果只有某个点异常,那就是局部链路运营商之间的问题,这时不用慌,按链路故障走工单找运营商排查,同时可以考虑临时切换DNS线路。这一步把故障定界定清楚之后,再往服务器里钻就踏实多了。
3. 服务器内部体检:负载、内存、磁盘、inode一个都不能少
定界到了服务器这一层,接下来做的就是在机器上"望闻问切"了。我有一套固定的开机五分钟命令序列,基本覆盖了服务器层面的绝大多数资源型故障。
3.1 开机五分钟的六道命令
登录服务器后,我习惯按以下顺序敲:
uptime free -h df -h df -i top -bn1 | head -20 dmesg -T | tail -30uptime看1分钟、5分钟、15分钟的负载均值。如果1分钟负载远高于15分钟,说明系统正在经历突发压力;如果三个值都很高,说明高负载已经持续很久了。对于Linux服务器,负载均值需要结合CPU核数来判断,比如8核的机器,负载超8就要警惕,16核的机器跑到12也未必有问题——这个判断逻辑很多人经常搞错。
free -h主要看内存余量和Swap使用情况。df -h检查磁盘空间,df -i检查inode。很多运维只查磁盘不查inode,这会在第3.2节重点说。top看一眼当前CPU和内存占用最高的进程,确认是不是某个进程异常吃满资源。dmesg则看内核日志,OOM记录、磁盘I/O错误、TCP丢包警告都在这里,是排查底层异常的黄金渠道。
3.2 磁盘满之外还有inode这个隐形坑
我得重点讲讲inode,这坑我踩过不止一次。有次客户反馈"网站无法上传图片,程序报磁盘写入失败",我上去一看df -h,磁盘还剩20G,怎么看都够用。后来查了一圈才发现,磁盘满了?不,是inode满了。
inode是Linux文件系统里用来记录文件元数据的数据结构,简单理解就是"档案编号"。每个文件(包括目录)都要消耗一个inode。如果磁盘里堆积了大量的小文件,比如PHP的session文件、各种缓存碎片、邮件队列,那么会出现磁盘空间还有空闲、但inode被消耗殆尽的情况。此时系统无法创建任何新文件,服务自然表现异常。
判断方法很简单:
df -i如果/dev/vda1的IUse%接近100%,那就是inode耗尽了。排查哪里堆积了大量小文件,可以用:
for i in /var/spool /tmp /var/log /home/* 目录; do find $i -type f | wc -l; done或者更精准地查找某个目录下文件数量排行。处理上一般就是清理过期的session、清空垃圾邮件队列、或者把缓存目录下超过30天的临时文件删除,并把定时清理脚本挂到crontab里。
3.3 内存与Swap:OOM杀进程的现场还原
内存耗尽的表现通常是:服务进程突然消失、页面请求全部502、free -h显示内存所剩无几,而Swap也基本没有。这时候不要光看Java/PHP进程本身,还要看是不是有其他进程把内存吃光了。
有一次我排查一个网站间歇性宕机,dmesg -T里看到了一行很关键的信息:
Out of memory: Kill process 2344 (php-fpm) score 832 or sacrifice child这就很明确了,PHP-FPM进程因为内存压力过大被内核的OOM Killer给"枪毙"了。处理办法是给php-fpm的pm.max_children调低,同时排查为什么单个PHP进程占用内存那么高——后来发现是个别接口存在死循环读取大文件的问题。不定位代码,光靠重启进程永远解决不了根本。
4. 应用层的排查重点:Web服务、日志、进程、配置
如果服务器硬件资源都正常,问题大概率就出在应用层。这里的"应用层"包括Web服务器(Nginx/Apache)、后端语言运行时(PHP-FPM/Java/Python/Golang)、以及业务代码本身。每一层都有它自己典型的故障模式。
4.1 服务在不在、端口通不通
到了服务器上,第一反应当然是确认服务进程和端口状态:
ps -ef | grep nginx ps -ef | grep php-fpm ss -tlnp | grep ':80\|:443\|:9000'如果进程不在了,看服务状态或直接重启。但这里有个经验:进程在,不代表服务是健康的。Nginx可能进程还挂着,但worker进程已经卡死;PHP-FPM可能master进程活着,但所有worker都在等待某个慢请求。我遇到过Nginx出现"所有worker都处于sleep状态,但CPU占用接近0"的情况,表现就是网站打开奇慢无比但没完全挂。
这时候需要看一眼错误日志和连接状态:
tail -f /var/log/nginx/error.log ss -t state established '( dport = :80 or sport = :80 )' | wc -l如果established连接数异常高,同时nginx日志频繁出现upstream timed out或connect() failed (111: Connection refused),那问题多半出在后端服务,比如PHP-FPM或Java服务已经没法正常工作了。
4.2 日志是事故现场的监控录像
排障到应用层,日志就是你唯一可信的信息来源。我总结过几个最常用的日志查看技巧:
# 看最近100行错误日志 tail -100 /var/log/nginx/error.log # 带时间戳持续跟踪 tail -f /var/log/nginx/access.log # 统计最近5分钟的访问量 awk -v date="$(date -d '-5 minutes' '+%d/%b/%Y:%H:%M')" '$4 > date' /var/log/nginx/access.log | wc -l # 找出耗时超过10秒的请求 awk 'int($NF) > 10000 {print $0}' /var/log/nginx/access.log | tail -20其中"找耗时超过10秒的请求"这条命令非常实用。Nginx的$request_time字段位于日志行的最后一个位置,单位是毫秒。如果发现某些请求的耗时特别离谱,哪怕数量很少,也可能是一个慢SQL拖垮了连接池,进而导致所有请求排队。
前端页面出现白屏时,如果访问日志里压根没有对应的请求记录,说明流量没到达后端,问题在更外层;如果日志有记录但返回5xx,说明到达了后端但处理失败;如果记录显示200但用户端仍然白屏,那可能是CDN缓存了错误页面,清一下缓存往往就好了。
4.3 配置文件:最后一个被怀疑的凶手
有一类故障很诡异:服务进程活着、日志也看不出明显错误、资源完全够用,但网站就是间歇性打不开。这种时候我一般会去对比配置文件,因为配置的坑往往藏得很深,要到最后才怀疑它。
有一次我们的Nginx配置在做灰度发布时,不小心把一个server块里的proxy_read_timeout从60改成了5。结果流量稍大或者后端处理稍慢,Nginx就主动断开连接,前端表现为"网站经常打开一半就报504"。从日志上看没有error,从资源上看没有瓶颈,最后是一行一行地diff配置才找到的元凶。
所以我的建议是:每次改配置前,先备份原文件并保留一个diff记录;遇到间歇性故障时,不要急着翻业务代码,先对比一下最近一次变更前后的配置差异。命令很简单:
diff /usr/local/nginx/conf/nginx.conf.bak /usr/local/nginx/conf/nginx.conf改完之后记得用nginx -t做语法校验,并nginx -s reload平滑加载。如果配置改挂了你没发现,那可能连服务都起不来,别问我是怎么知道的。
5. 数据库故障往往最隐蔽:连接数、锁等待、慢查询逐个看
数据库几乎是"网站打不开"故障里最热门的元凶了,尤其是用了MySQL的场景。很多应用层的异常,追根溯源都会回到数据库这一层。而且数据库故障的表现往往不那么直观——页面返回502、接口超时、系统间歇性卡顿,你可能查了半天代码,最后才发现是数据库连接池被拖垮了。
5.1 连接数被打满的典型表现
MySQL连接数满了的经典报错是:
Too many connections但很多时候应用的报错不一定直接显示这句话,而是显示Connection refused、SQLSTATE[HY000] [2002]之类的信息,因为连接池在尝试建立新连接时就已经失败了。此时你需要确认MySQL当前的连接情况:
SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections'; SHOW PROCESSLIST;如果Threads_connected长期贴着max_connections上限,那就是连接数被打满了。常见原因有两个:
- 某个接口的代码忘记释放连接(连接泄漏),导致连接只增不减。
- 某段时间流量突增,连接池的最大连接数不够用。
处理方法是先临时调大max_connections(注意不要一下子调到远超实际内存承载能力),最稳妥的是重启有问题的应用服务,释放挂起的连接。然后立刻去查连接数异常增长的来源,通常SHOW PROCESSLIST里能看到大量来自同一台应用服务器的连接,集中在同一个库。再配合慢查询日志,基本就能定位到是哪个SQL拖慢连接池了。
5.2 慢查询与锁等待
即使连接数没满,数据库也可能因为某一条慢SQL把整个库拖垮。最典型的场景是:一张大表没有走索引,一个全表扫描查询就要几秒,业务请求一多,所有请求都在等这条SQL执行完,数据库的CPU飙升,查询全部变慢。
排查慢查询,先看慢查询日志有没有开:
SHOW VARIABLES LIKE 'slow_query_log'; SHOW VARIABLES LIKE 'long_query_time';没开的话,建议立刻打开:
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 2;另外,锁等待问题也非常常见。我处理过一个线下故障:某业务表被一个长事务锁住,其他所有操作这条表的请求全部堵在Waiting for table metadata lock或Lock wait timeout exceeded。这种情况在SHOW PROCESSLIST里能直接看到大量状态为Waiting for ...的会话。
遇到锁等待,先找出持有锁的源头事务ID,然后让业务方评估是否需要杀掉该事务。大多数时候Apache/Nginx层怎么调都没用,只有把锁释放掉,流量才会恢复。
5.3 主从同步延迟
如果网站架构里走了读写分离,还有一个容易被忽略的坑:主从同步延迟。比如用户提交订单后,立即去查订单列表,如果查询走的是从库,而从库还没同步到最新数据,就会看到"数据消失"或"页面数据不全"的假象。更严重时,如果从库同步中断太久、binlog积压严重,从库上的查询会越来越慢,最终拖垮业务。
排查同步延迟,在从库上执行:
SHOW SLAVE STATUS\G重点看Seconds_Behind_Master,数值越大说明延迟越严重。如果是持续性的高延迟,优先检查从库服务器的磁盘I/O、网络带宽,以及有没有大事务在主库上执行。解决办法通常是给大事务拆批,或者把部分分析型查询直接指向主库,临时缓解透读压力。
6. 让SOP从灭火器变成防护网:监控指标与故障演练
排查SOP最大的价值不只是让你在故障发生时能按图索骥,而是把"救火"变成"防火"。如果你总是等到报警了才开始排查,那即使SOP写得再完美,网站的稳定性也是脆弱的。更好的做法是,把这篇SOP里的关键判断点都变成监控指标,让系统在你还没接到用户投诉之前就发出告警。
6.1 核心指标的监控清单
我建议至少给下面这些指标配上监控、告警和可视化:
| 监控对象 | 指标体系 | 建议告警阈值 |
|---|---|---|
| 服务器 | CPU使用率、负载均值、内存/磁盘/inode | 负载持续5分钟超核数、磁盘使用率超85% |
| Nginx | 活跃连接数、5xx状态码比例、request_time | 5xx比例超过5%、平均耗时超3秒 |
| 应用 | PHP-FPM/Java进程状态、队列堆积数 | 进程挂掉、队列积压超过阈值 |
| 数据库 | Threads_connected、慢查询数、主从延迟 | 连接数超80%、慢查询突增、延迟超10秒 |
监控工具方面,中小团队可以用开源方案,比如Prometheus + Alertmanager + Grafana这套组合。如果不想自己搭,云厂商自带的云监控也够用,关键是把告警渠道配上——电话、短信、钉钉/企业微信机器人,任选两三个,避免单点渠道失效。
6.2 故障演练与复盘:让SOP活起来
写好的SOP如果只在故障时打开看,它永远不会变成团队的能力。我建议每季度至少做一次故障演练,挑一个非业务高峰期的时段,人为制造一两个故障场景:关掉一台应用服务器、把数据库连接池调小、或者模拟DNS解析异常,然后让值班的同学按SOP走一遍,记录耗时和卡壳的地方。
演练之后最重要的一件事是复盘。每次真实故障处理完,一定要回答三个问题:
- 当前SOP里哪些步骤是有效的?哪些步骤根本用不上?
- 本次故障有没有在监控层面提前发现?如果没有,缺什么指标?
- 有没有什么工具或自动化手段能减少下次定位的时间?
我自己就经常干这种事:复盘完之后,把每次故障的根因和处置方法写进知识库,并同步更新SOP。这样跑过一两次完整循环之后,SOP就从一份死文档变成了真正有生命力的流程。
这套排查思路,核心就一句话:分清层次、按序深入、经验沉淀、工具固化。真把这一套跑顺了,你会发现网站没有那么容易挂,就算挂了你也不再害怕——你手里已经有了一套清晰的路线图,知道每一步该看什么、怎么判断、往哪走。别小看这个"按部就班",在凌晨两点的告警声里,它就是你最可靠的伙伴。