CPU不高但请求排队,这个现象在运维排查里有个精准的描述:线程在等,不在算。CPU使用率低只说明处理器没有持续运算压力,请求卡在了某个"等待"环节上,而不是卡在计算上。对于香港服务器,这个等待可能发生在从进程池到跨境链路的任何一个节点。
** 第一层:应用进程池耗尽,请求在门外排队**
这是最常见也最容易被忽略的原因。以PHP-FPM为例,pm.max_children设定了同时能处理请求的进程数上限。当并发请求数超过这个上限,新来的请求不会被CPU处理------因为没有空闲的worker去接它们,它们只能待在监听队列里等待。
此时CPU看起来很闲,因为确实没有进程在消耗它。用ss -lnt查看监听队列的Recv-Q列,如果数值持续大于0,说明请求正在排队。PHP-FPM的状态页也能直接看到listen queue的堆积情况。调大进程池是解法,但需要同时评估内存余量------每个PHP进程占用40到80MB内存,池子开太大可能触发OOM。
** 第二层:数据库成为隐形瓶颈,应用在等回话**
应用服务器的CPU很低,但请求就是慢,大概率是在等数据库。MySQL连接数耗尽、慢查询占着连接不释放、或者连接池被打满,应用层的worker只能阻塞在那里等数据库返回结果。
这种情况的迷惑性在于:应用服CPU闲,但数据库那头可能CPU拉满或磁盘I/O打满。排查方向不是加应用服务器的CPU,而是看慢查询日志、数据库连接数水位、以及数据库本身的资源使用。如果SQL没有走索引、全表扫描,一条查询就能拖住一个worker几十秒,后续请求全部排队。
** 第三层:磁盘I/O阻塞,进程卡在等读写**
iowait在CPU统计里不算"忙",所以磁盘瓶颈在CPU监控上几乎看不出来。当磁盘读写队列堆积,进程全部卡在等待磁盘操作完成,CPU空转,请求排队。
香港服务器如果使用普通云盘,大量日志写入、数据库读写、文件备份都可能打满磁盘IO上限。用iostat -x 1观察%util和await,如果%util接近100%且await持续偏高,磁盘就是瓶颈。另一个隐蔽的触发点是内存不足导致Swap频繁读写------物理内存耗尽后,系统把数据换到磁盘,读写速度骤降,CPU依然不忙,但所有请求都在等Swap。
** 第四层:单线程或锁竞争,只有一两个核在干活**
整体CPU使用率不高,但某一个逻辑CPU核心长期接近满载。这种情况在单线程模型的应用、或者存在严重锁竞争的代码中很常见。请求被串行化处理,其他CPU核心闲着,但所有请求都堵在那把锁或那个线程后面。
用mpstat -P ALL查看各核心的独立使用率,而不是只看总CPU百分比。如果某个核心持续100%而其他核心接近0%,单线程瓶颈基本可以确认。加CPU核心数在这种场景下没有帮助,因为程序本身用不上。
** 第五层:外部依赖超时,worker被占着干等**
请求卡在调用第三方API、支付接口、短信网关、或者内部另一个微服务上。对方的响应慢或超时,你的worker就被占着阻塞等待,连接池逐渐耗尽,后续请求全部排队。CPU闲,是因为线程都block在等外部响应。
PHP-FPM的慢日志可以直接定位这类问题:如果慢请求的调用栈顶部出现curl_exec或stream相关函数,说明卡在外部HTTP调用上;如果出现PDO或mysqli,说明卡在数据库。看慢日志比盯着CPU监控有用得多。
** 排查顺序**
不要从CPU监控出发,从请求卡在哪一层出发。先用ss -lnt看连接队列,再看PHP-FPM或应用进程池的listen queue,然后查数据库慢日志和连接数,用iostat看磁盘%util,用mpstat -P ALL看单核分布,最后看慢日志里是否有外部调用的阻塞栈。七步走完,卡点会自己浮出来。