第5章 应用:机器没打满,也可能先从应用侧出问题

很多看起来像系统瓶颈的问题,根子其实在请求路径、锁竞争和运行时行为。

本页目录

先回到请求和并发模型

第 5 章先提醒你,很多“系统慢”根本不是从资源域开始长出来的,而是从应用的请求画像、并发模型和运行时行为开始变形。

机器没打满,不代表应用没在排队。多个下游同时变慢,也不代表一定是多个独立根因。

先看请求、并发、排队和下游等待

应用侧最先要确认的,不是“机器忙不忙”,而是下面这些:

  • 请求是不是变重了
  • 请求量是不是变了
  • 队列是不是开始堆了
  • 线程池、连接池、协程池是不是卡住了
  • 锁、重试、批量大小、缓存命中是不是变了
  • 慢的是执行本身,还是等待外部依赖

这几件事没看,后面很多系统指标都会被读歪。

这里最该加的一条现场纪律是: 先做线程状态分析,再猜根因。应用慢的时候,不要先凭经验判断“像锁”“像网络”“像数据库”,而要先把线程时间硬拆成执行、可运行、锁等待、磁盘 I/O、网络 I/O、睡眠和空闲这些状态。主观上的“慢”,要先变成线程时间分布,后面的判断才站得住。

别把应用里的排队和锁竞争误判成系统问题

最常见的错法有四种:

  • 机器不忙,就以为不是应用问题
  • 多个下游都慢,就以为有多个根因
  • 看到网络超时,就跳去查网络
  • 看到数据库变慢,就跳去查数据库

这些错法都跳过了一步:先看应用有没有把请求结构和等待结构改坏。

应用层最会伪装成哪些系统问题

应用层很会伪装。常见伪装有这几类:

  • 伪装成 CPU 问题:真正的问题是热点请求、忙循环、锁竞争、低效代码路径
  • 伪装成 I/O 问题:真正的问题是批量策略、同步写、写放大、调用顺序
  • 伪装成网络问题:真正的问题是重试放大、超时设置、连接复用、下游调用模式
  • 伪装成数据库问题:真正的问题是请求形态变了、并发策略变了、缓存命中变了

所以第 5 章不是让你忽略系统,而是让你先别太快甩锅给系统。

还有一个很容易踩的坑是 off-CPU 误判。线程大量阻塞在 socket、条件变量或其他等待点上,不自动等于网络坏了或锁炸了。很多时候,这只是线程池空闲地等下一个请求。off-CPU 图只能说明“没在 CPU 上”,还不能直接说明“卡住了什么瓶颈”。

一个最典型的错法:机器不忙,但接口越来越慢

现场很常见:CPU 没打满,磁盘也不算忙,网络看起来也没明显炸,但接口 P99 一直变差。

这时候最容易走两条歪路:

  • 直接把锅甩给云平台、网络或内核
  • 因为机器不忙,就判断“系统没问题”

更稳的做法是先回应用侧看:

  • 请求是不是比以前更重
  • 队列是不是更长了
  • 池子是不是开始争用了
  • 慢的是代码执行,还是等待外部依赖

很多看起来像系统抖动的问题,到这里就已经能缩小很多。

应用章还有一条很硬的工程前提: 如果编译型程序在构建时把 frame pointer 去掉了,现场 profile 和追锁时经常只能抓到一堆 [unknown] 的断栈。真想让应用侧可观测,保留帧指针不是优化建议,而是最低配置。

先把请求、等待和下游关系理顺

先把请求画像和等待结构说清

以后遇到“机器还行,但接口慢”,先回答这 4 个问题:

  1. 请求本身变重了吗。
  2. 并发和排队变了吗。
  3. 池子、锁和缓存命中变了吗。
  4. 时间花在执行上,还是花在等待外部依赖上。

先列出自己业务里最会伪装的 3 类应用问题

至少写下这三类:

  • 一类最像 CPU 问题的应用行为
  • 一类最像网络问题的应用行为
  • 一类最像数据库问题的应用行为

以后一看到对应症状,就先回应用侧比对。

多个下游一起慢时,先怀疑上游请求形态变了

如果数据库、缓存、RPC、GC、网络同时都“有点不对”,先别急着各自认领根因。先看是不是应用层先改坏了请求形态、重试行为或并发策略。

应用侧看清以后,再决定进哪个资源域

第 5 章解决的是“哪些慢该先回应用侧看”。第 6 章接着进入最容易被误判的资源域:CPU。