本页目录
先回到请求和并发模型
第 5 章先提醒你,很多“系统慢”根本不是从资源域开始长出来的,而是从应用的请求画像、并发模型和运行时行为开始变形。
机器没打满,不代表应用没在排队。多个下游同时变慢,也不代表一定是多个独立根因。
先看请求、并发、排队和下游等待
应用侧最先要确认的,不是“机器忙不忙”,而是下面这些:
- 请求是不是变重了
- 请求量是不是变了
- 队列是不是开始堆了
- 线程池、连接池、协程池是不是卡住了
- 锁、重试、批量大小、缓存命中是不是变了
- 慢的是执行本身,还是等待外部依赖
这几件事没看,后面很多系统指标都会被读歪。
这里最该加的一条现场纪律是: 先做线程状态分析,再猜根因。应用慢的时候,不要先凭经验判断“像锁”“像网络”“像数据库”,而要先把线程时间硬拆成执行、可运行、锁等待、磁盘 I/O、网络 I/O、睡眠和空闲这些状态。主观上的“慢”,要先变成线程时间分布,后面的判断才站得住。
别把应用里的排队和锁竞争误判成系统问题
最常见的错法有四种:
- 机器不忙,就以为不是应用问题
- 多个下游都慢,就以为有多个根因
- 看到网络超时,就跳去查网络
- 看到数据库变慢,就跳去查数据库
这些错法都跳过了一步:先看应用有没有把请求结构和等待结构改坏。
应用层最会伪装成哪些系统问题
应用层很会伪装。常见伪装有这几类:
- 伪装成 CPU 问题:真正的问题是热点请求、忙循环、锁竞争、低效代码路径
- 伪装成 I/O 问题:真正的问题是批量策略、同步写、写放大、调用顺序
- 伪装成网络问题:真正的问题是重试放大、超时设置、连接复用、下游调用模式
- 伪装成数据库问题:真正的问题是请求形态变了、并发策略变了、缓存命中变了
所以第 5 章不是让你忽略系统,而是让你先别太快甩锅给系统。
还有一个很容易踩的坑是 off-CPU 误判。线程大量阻塞在 socket、条件变量或其他等待点上,不自动等于网络坏了或锁炸了。很多时候,这只是线程池空闲地等下一个请求。off-CPU 图只能说明“没在 CPU 上”,还不能直接说明“卡住了什么瓶颈”。
一个最典型的错法:机器不忙,但接口越来越慢
现场很常见:CPU 没打满,磁盘也不算忙,网络看起来也没明显炸,但接口 P99 一直变差。
这时候最容易走两条歪路:
- 直接把锅甩给云平台、网络或内核
- 因为机器不忙,就判断“系统没问题”
更稳的做法是先回应用侧看:
- 请求是不是比以前更重
- 队列是不是更长了
- 池子是不是开始争用了
- 慢的是代码执行,还是等待外部依赖
很多看起来像系统抖动的问题,到这里就已经能缩小很多。
应用章还有一条很硬的工程前提: 如果编译型程序在构建时把 frame pointer 去掉了,现场 profile 和追锁时经常只能抓到一堆 [unknown] 的断栈。真想让应用侧可观测,保留帧指针不是优化建议,而是最低配置。
先把请求、等待和下游关系理顺
先把请求画像和等待结构说清
以后遇到“机器还行,但接口慢”,先回答这 4 个问题:
- 请求本身变重了吗。
- 并发和排队变了吗。
- 池子、锁和缓存命中变了吗。
- 时间花在执行上,还是花在等待外部依赖上。
先列出自己业务里最会伪装的 3 类应用问题
至少写下这三类:
- 一类最像 CPU 问题的应用行为
- 一类最像网络问题的应用行为
- 一类最像数据库问题的应用行为
以后一看到对应症状,就先回应用侧比对。
多个下游一起慢时,先怀疑上游请求形态变了
如果数据库、缓存、RPC、GC、网络同时都“有点不对”,先别急着各自认领根因。先看是不是应用层先改坏了请求形态、重试行为或并发策略。
应用侧看清以后,再决定进哪个资源域
第 5 章解决的是“哪些慢该先回应用侧看”。第 6 章接着进入最容易被误判的资源域:CPU。