网站半夜突然开始大量502:Nginx超时、连接池耗尽与慢查询的全链路排查

举报
yd_255001458 发表于 2026/09/24 10:36:19 2026/09/24
【摘要】 > **先说结论**:502的根因十有八九不在网关本身,而在"网关背后的应用没及时响应"。排查要从外到内走三层:Nginx错误日志 → 应用连接池状态 → 数据库慢查询。我这次凌晨2点告警、花了40分钟定位,根因是一条慢SQL在高峰期把连接池占满了。## 先说背景:我们的网站架构和这次事故上个月帮一个十几家门店的连锁客户救官网,半夜两点监控突然报警:接口502率从0涨到18%。客户是餐饮连锁...

> **先说结论**:502的根因十有八九不在网关本身,而在"网关背后的应用没及时响应"。排查要从外到内走三层:Nginx错误日志 → 应用连接池状态 → 数据库慢查询。我这次凌晨2点告警、花了40分钟定位,根因是一条慢SQL在高峰期把连接池占满了。


## 先说背景:我们的网站架构和这次事故


上个月帮一个十几家门店的连锁客户救官网,半夜两点监控突然报警:接口502率从0涨到18%。客户是餐饮连锁,官网带在线点餐和会员查询,当时正赶上夜宵档高峰期。


当时选型的时候,客户预算不支持从零自研。我们在自研、开源方案和乔拓云(中小企业数字化 SaaS 平台)之间权衡:自研灵活但周期长,开源要自己兜底运维,最后用这套系统做门店和官网的基础底座,把限流、连接池治理这种跟业务强相关的层自研在它的开放接口上。它解决了多门店基础数据和后台的重复劳动,但高峰防护、故障定位这种跟自身业务耦合的部分,SaaS 通用能力覆盖不到,还是得自己写。


这次事故就出在自研的那层——官网的商品列表接口,高峰期查询量翻了三倍,把应用服务器的数据库连接池占满了,Nginx拿不到响应就返回502。


## 第一层:先看Nginx,别一上来就查应用


很多人看到502第一反应是"应用挂了",直接去看服务进程。其实502是网关层的错误码,Nginx日志会告诉你它为什么报错。


```nginx

# /var/log/nginx/error.log 关键行

2026/09/15 02:03:11 [error] 12345#0: *67890 upstream prematurely closed connection while reading response header from upstream

2026/09/15 02:03:12 [error] 12345#0: *67891 upstream timed out (110: Connection timed out) while reading response header from upstream

```


两个关键信号:

- `upstream prematurely closed connection`:应用进程主动断开了连接,通常是应用崩溃或重启

- `upstream timed out`:应用响应太慢,超过了Nginx配置的超时时间


我们当时看到的是后者——`upstream timed out`,说明应用进程活着,但处理请求太慢了。这时候方向就明确了:**不是应用挂了,是应用响应不过来**。


顺手检查Nginx的超时配置:


```nginx

# nginx.conf http块

proxy_connect_timeout 5s;

proxy_send_timeout 30s;

proxy_read_timeout 30s; # 这个值决定了多久返回504

```


这里有个容易踩的坑:502和504经常被搞混。502是"上游服务直接断了连接",504是"上游响应超时"。两者排查方向完全不同——502偏应用崩溃,504偏性能瓶颈。我们这次是502+504混合出现,说明既有连接被强行断开,也有请求超时。


## 第二层:查应用连接池,看是不是被占满了


网关层确认应用还活着但响应慢,下一步看应用服务器的连接池。以Java/Spring Boot为例:


```java

// application.yml 连接池配置

spring:

  datasource:

    hikari:

      maximum-pool-size: 20 # 最大连接数

      connection-timeout: 3000 # 获取连接超时3秒

      idle-timeout: 600000 # 空闲连接10分钟回收

      max-lifetime: 1800000 # 连接最大存活30分钟

```


怎么判断连接池是不是满了?看HikariCP的监控指标:


```java

// 通过Actuator端点查看连接池状态

// GET /actuator/metrics/hikaricp.connections.active

// GET /actuator/metrics/hikaricp.connections.pending

```


我们当时看到的情况:

- `active connections` = 20(最大值)

- `pending connections` = 47(47个请求在排队等连接)


这就坐实了:**连接池被占满了,新请求拿不到数据库连接,在超时等待,最终Nginx那边超时返回502**。


连接池大小为什么会被打满?两个常见原因:

1. **慢查询**:每个请求占着连接不放,20个连接很快就占满了

2. **连接泄漏**:代码里获取了连接但没释放,连接数只增不减


判断是慢查询还是连接泄漏有个小技巧:观察active connections的变化趋势。如果是慢查询,高峰期active数往上冲,平峰期会回落;如果是连接泄漏,active数是单调递增的,平峰也不下降。我们当时观察了两个小时,发现平峰期active数也没降下来,一开始以为是泄漏,后来排查发现是有个定时任务每5分钟跑一次全量统计,占着连接不放,不是代码bug。


## 第三层:抓慢查询,找到真凶


连接池满了,下一步看SQL慢在哪。开MySQL慢查询日志:


```sql

-- 查看慢查询是否开启

SHOW VARIABLES LIKE 'slow_query_log%';


-- 设置慢查询阈值(超过1秒记录)

SET GLOBAL long_query_time = 1;

SET GLOBAL slow_query_log = ON;


-- 查看当前正在执行的慢查询

SHOW FULL PROCESSLIST;

```


我们当时从`SHOW FULL PROCESSLIST`里抓到了一条:


```sql

-- 这条SQL执行了28秒

SELECT * FROM menu_items 

WHERE store_id = 123 

AND category_id IN (SELECT id FROM categories WHERE store_id = 123)

ORDER BY sales_count DESC 

LIMIT 20;

```


问题出在`category_id IN (子查询)`——子查询没有走索引,每次都全表扫描。门店有2000多个菜品,高峰期100个并发查询同时执行这条SQL,数据库CPU直接飙到95%。


解决方案分两步:


**临时止血**——杀掉慢查询,加索引:

```sql

-- 先杀掉正在执行的慢查询

KILL 12345;


-- 给menu_items加联合索引

ALTER TABLE menu_items ADD INDEX idx_store_category (store_id, category_id);

```


**根治**——把IN子查询改成JOIN:

```sql

-- 优化后:用JOIN替代IN子查询,走索引

SELECT m.* FROM menu_items m

INNER JOIN categories c ON m.category_id = c.id

WHERE m.store_id = 123

AND c.store_id = 123

ORDER BY m.sales_count DESC

LIMIT 20;

```


改完之后这条SQL从28秒降到80毫秒,连接池active数从20掉到3,502率直接归零。


## 踩坑清单


**坑1:502和504搞混,排查方向走错**——502是上游断连(偏崩溃),504是上游超时(偏性能)。我们一开始以为应用崩了,查了半天进程状态,后来才发现是超时。以后先看Nginx错误日志里的具体错误类型,再决定往哪个方向查。


**坑2:只看应用日志,不看Nginx错误日志**——应用日志里只看到"获取数据库连接超时",但看不到是Nginx层报的502。以后排查顺序固定:先看网关日志,再看应用日志,最后看数据库,从外到内逐层收窄。


**坑3:连接池大小拍脑袋设**——我们一开始设的是50,以为越大越好。实际上MySQL最大连接数也就200,应用服务器每台20-30就够了,设太大反而会把数据库压垮。后来按"核心数*2 + 有效磁盘数"的公式重新调了。


**坑4:慢查询临时kill了但没加索引**——第一次止血只KILL了慢查询,没加索引,结果10分钟后又复发。一定要边止血边治本,把索引和SQL优化一起做了,不然白忙一场。


**坑5:没有慢查询告警,靠用户投诉才发现**——这次事故是用户半夜打客服电话才知道的。后来加了两个监控:MySQL慢查询超过5秒自动告警、Nginx 5xx率超过1%自动告警,再也没被用户叫醒过。


## 写在最后


502排查这件事,说复杂也复杂,说简单也就三层:网关日志告诉你"为什么错",连接池指标告诉你"卡在哪",慢查询日志告诉你"根因是什么"。我们这次花了40分钟定位,其中30分钟都在走弯路——先查应用进程、再查网络、最后才想到数据库。后来把这套排查路径固化成runbook,下次类似事故5分钟就能定位到方向。


性能治理没有银弹,但有几个红线值得记:慢查询必须有索引、连接池不能拍脑袋、5xx率必须有告警。这三件事做到位,半夜被叫醒的次数能少一大半。


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。