高并发下服务器宕机怎么排查
并发一上来服务器就宕,第一反应别急着重启。得先分清到底是谁挂了:Nginx、应用、Nginx 误判应用、还是数据库拖垮了应用。这篇顺着 浏览器→WAF→LB→Nginx→应用 整条链路捋排查思路,再讲怎么层层做削峰,把洪峰挡在外面。
并发量突然飙上来,服务器扛不住,宕了。你怎么办?
很多人第一反应是重启。但重启只是把症状按下去,洪峰还在,一会儿又给你宕一次。
真正该做的,是先搞清楚到底是哪一环先崩的,然后再谈怎么让整条链路扛住洪峰。这篇我们就顺着请求链路,把排查和防御都捋一遍。
先把整条链路摆出来
排查之前,得先知道一个请求从用户到应用,中间经过了哪些关卡。典型的链路是这样:
客户端(浏览器)
|
WAF (Web 应用防火墙,挡攻击/恶意流量)
|
LB (负载均衡,把流量分发到多台)
|
Nginx (反向代理,转发给后端应用)
|
应用 (真正处理业务的服务)
|
数据库 (应用背后的数据存储)
请求一层层往里走。宕机,一定是这条链路上某个环节先扛不住了。 排查的本质,就是揪出最先崩的那一环。
别一上来就盯着应用,也别一上来就怪 Nginx。顺着链路一段段查,才不会冤枉好人。
到底是谁挂了
用户看到的可能都是 502、504 或者干脆打不开。但底下的真相,可能是好几种完全不同的情况。我们一个个排。
情况一 Nginx 自己挂了
先看最前面的转发层。Nginx 有没有可能是它自己扛不住?
有。并发太高,Nginx 的连接数打满、worker 进程或文件句柄耗尽,它自己就先趴了。
怎么判断:看 Nginx 进程还在不在、看它的错误日志有没有“too many open files”“worker_connections not enough”这类报错。
如果是 Nginx 自己的资源瓶颈,那问题在它身上,得调它的连接数、句柄数配置。
情况二 应用真的挂了
更常见的是,Nginx 好好的,是后面的应用扛不住了。
并发一大,应用线程池被打满、内存被撑爆(OOM)、或者直接崩溃退出。这时候 Nginx 转发过去,后面没人接,就报 502。
怎么判断:去应用机器上看进程在不在、看应用日志有没有异常、看 CPU 内存是不是被打满。
(这块的具体排查,我之前写过一篇专门讲 502 的,思路是相通的——看日志、查进程端口、直连后端。)
情况三 Nginx 误以为应用挂了
这种最坑,也最容易被忽略:应用其实还活着,只是变慢了,结果被 Nginx 当成挂了。
并发高的时候,应用响应变慢,处理一个请求要好几秒。而 Nginx 对后端是有超时时间的,等超过这个时间应用还没回,Nginx 就认为“这后端不行了”,直接返回 504,甚至把它从可用列表里摘掉。
于是明明应用还在苟延残喘,却被判了死刑。
怎么判断:绕过 Nginx 直连应用,如果直连能通(就是慢),那应用没死,是 Nginx 的超时或健康检查太敏感,误杀了。
情况四 数据库拖垮了应用
还有一种,根子更深:应用本身没毛病,是数据库先扛不住,反过来拖死了应用。
并发一大,慢查询堆积、数据库连接池被占满。应用的每个请求都卡在“等数据库返回”上,一直不释放线程。慢慢地,应用的线程全被这些等待占死,新请求进不来,应用看着就像挂了。
这是最隐蔽的一种:你盯着应用查了半天,发现应用代码没问题,其实病根在更后面的数据库。
怎么判断:看数据库的连接数、慢查询日志、CPU。看应用的线程栈是不是大量卡在数据库调用上。
一句话总结这四种:Nginx 崩、应用崩、应用被误判、数据库拖累——症状可能都是打不开,但病根天差地别。排查的关键,是顺着链路一层层确认每一环的死活,别停在第一个看到的表象上。
光排查不够 得层层削峰
查出病根、救活了这一次,不代表下次洪峰来了不再宕。
真正稳的系统,是不让洪峰一路裸奔冲到最脆弱的那一环(通常是数据库)。思路就一个词:削峰——在链路的每一层都做好拦截,把汹涌的流量一层层削减、摊平。
我们顺着链路,从外往里,每一层都设一道闸:
浏览器 -> 前端限制:按钮点一次就置灰,防重复提交
|
WAF -> 挡掉恶意流量、爬虫、明显的攻击请求
|
LB -> 把流量均摊到多台,别让一台独扛
|
Nginx -> 限流:限制每秒请求数、每 IP 连接数
|
应用 -> 限流 + 熔断降级 + 队列削峰
|
数据库 -> 被前面层层保护,只接得住的量
一层层拆开说:
- 浏览器端:最外层就先拦一道。按钮点击后立刻置灰、加防抖,别让用户狂点制造无谓的重复请求。成本最低、最该做
- WAF:把恶意流量、爬虫、攻击性请求挡在门外,别让这些垃圾流量占用后面的资源
- LB(负载均衡):把流量摊到多台机器上,一台扛不住就多上几台,别让单点独自硬抗
- Nginx:这里能做限流,限制每秒进来的请求数、限制单个 IP 的连接数,超出的直接挡回去或排队
- 应用层:这是削峰的重头戏,几个手段一起上——限流(超过阈值的请求直接拒绝,保护自己)、熔断降级(下游扛不住时,快速返回兜底结果,不硬等)、队列削峰(把瞬时洪峰的请求先塞进消息队列,应用按自己的节奏慢慢消费,把尖峰摊平成平缓的流)
- 数据库:它是最脆弱、最该被保护的一环。前面每一层都削一点,真正落到数据库的量,才是它接得住的
核心思想:越往里越脆弱,所以越往外越要拦。 每层削掉一部分,层层递减,洪峰到数据库时已经变成小溪了。
队列削峰再多说一句
削峰里最能体现“摊平”思想的,是队列削峰,单拎出来讲讲。
想象一下秒杀:一秒钟涌进来 10 万个请求,应用每秒只能处理 1 万个。硬接必死。
队列削峰的做法:把这 10 万请求先全塞进一个消息队列里排队,应用不慌不忙,按自己每秒 1 万的能力从队列里取出来处理。
瞬时洪峰: ||||||||||| (一秒 10 万涌入)
|
消息队列 (先囤着,排队)
|
应用消费: | | | | | | (按 1 万/秒的稳定节奏取)
这样一来,洪峰被队列缓冲住了,应用面对的永远是自己能处理的平稳流量,不会被瞬间冲垮。代价是用户可能要等一会儿(请求在排队),但总比整个系统宕了强。
这就是“削峰填谷”——把陡峭的尖峰削下来,填到后面的平缓时段去。
小结
高并发宕机这道题,答的不是“重启”,而是先排查、再防御两步。
排查——顺着链路揪出最先崩的那一环:
- Nginx 自己挂(连接数/句柄耗尽)
- 应用真挂了(线程满、OOM、崩溃)
- 应用被 Nginx 误判(应用只是慢,被超时误杀,直连能验证)
- 数据库拖垮应用(慢查询/连接池满,应用线程全卡在等库上)
防御——沿着 浏览器 →WAF→LB→Nginx→ 应用 → 数据库 层层削峰:
- 越往里越脆弱,越往外越要拦;每层削一点,洪峰到数据库时已成小溪
- 应用层重点上限流、熔断降级、队列削峰;队列把瞬时尖峰缓冲成平稳流
说到底,扛高并发靠的不是某一处的猛药,是整条链路环环相扣、层层设防。想清楚每一环的死活怎么查、每一层的闸怎么设,再遇到洪峰,你就有底气不慌了。那就照着这条链路,把自己系统的每道闸都盘一遍吧。
