找回密码
立即注册
搜索
发回帖 发新帖

6430

积分

0

好友

816

主题
发表于 半小时前 | 查看: 4| 回复: 0

线上接口变慢时,最容易犯的错误是把某个高指标直接当作根因:Load 高就加 CPU,free 少就清缓存,磁盘 %util=100 就换盘。性能排障需要的是证据链:业务延迟什么时候变化,哪些资源出现等待,哪个进程或依赖制造了等待,处置后是否在相同负载下恢复。

本文面向 Linux 主机与 Kubernetes 节点。工具示例以 Bash、procps-ng、sysstat、iproute2 和 GNU coreutils 为基础;权限、字段和采集方式随版本变化。容器内部的资源限额需要另外查看 cgroup,宿主机空闲不能证明容器没有被限流。文中地址、PID、阈值与案例数值均为示例。

1. 先确认影响范围,再采集现场

先回答三个问题:只有一台机器慢,还是所有机器都慢?只有某个接口慢,还是 SSH、数据库和批任务都慢?开始时间是否与发布、备份或流量变化一致?多台机器同时异常时,应同时检查公共数据库、共享存储、网络出口和共同变更。

在目标主机执行:

export LC_ALL=C
date -Is
uptime
nproc
free -h
vmstat 1 5
mpstat -P ALL 1 3
iostat -y -x 1 3
sar -n DEV,EDEV 1 3
ss -s
sudo journalctl -k --since '-30 min' --no-pager

mpstat、iostat、sar 来自 sysstat;未安装时先利用已有监控和基础工具,避免在故障高峰临时安装大量依赖。vmstat 首份报告中的速率通常是开机以来平均值,后续报告才对应采样间隔;iostat -y 跳过开机以来报告;带间隔的 mpstat 输出按采样窗口解读,不套用“第一行一定是开机平均”的规则。[1][2]

上述命令主要读取状态,但仍有 CPU、I/O 与输出开销;只读不等于零风险。特别是递归 du/find、抓包、线程栈和 profiling,要控制范围与时长。跨主机对账应统一记录带时区的时间,并检查时钟同步;时区相同不等于时钟同步。

2. 指标应该怎么读

2.1 Load 反映任务数量,不是 CPU 百分比

Linux Load Average 近似反映可运行任务与参与负载统计的不可中断睡眠任务数量,1、5、15 分钟值是平滑趋势,不是简单窗口算术平均。D 状态常与 I/O 有关,但也可能来自其他内核等待,不能一律解释为本地磁盘故障。

vmstat r 是可运行任务数,b 是阻塞等待 I/O 的任务数。持续 r 高且 CPU 忙,才支持 CPU 竞争判断;b 高时结合设备延迟、任务内核栈和远端存储状态定位。单次快照无法确定因果关系。

比较 CPU 数量时,还要考虑 CPU affinity、cpuset 和 cgroup CPU quota。主机 64 核不意味着一个限额为 2 CPU 的容器可以使用 64 核。

2.2 CPU 时间分解

指标 排查方向 解读边界
user / nice 用户态计算、GC、代码热点 高占用也可能是正常业务计算
system 内核路径、系统调用、内存管理 需要进一步采样,不能凭占比指定根因
irq / softirq 硬中断与软中断 看每核分布;软中断不限于网络
iowait I/O 等待相关的 CPU 时间记账 不是磁盘利用率;低值不能排除 I/O 慢
steal 虚拟 CPU 未获得运行时间 表明虚拟化调度影响,不能单凭它证明宿主机超卖
idle CPU 空闲 主机空闲时仍可能有单核热点或容器 quota 限流

iowait 的记账在多核系统上有局限,应结合存储指标解读。高上下文切换数也不是独立故障证据,要与线程数、业务负载、CPU 占用和延迟一起比较。[2]

2.3 内存看 available,也看回收与限额

MemAvailable 是“不发生换页时可用于新应用”的估计,不是保证可以立即分配的字节数。文件缓存部分可回收,但脏页写回、不可回收 slab、锁定内存、NUMA 局部压力和容器内存限制都可能改变实际分配行为。

swap 已使用不等于正在发生换页风暴。vmstat si/so 的持续增长、主缺页、直接回收和业务延迟同时异常,才支持换页影响性能的结论。少量换页不一定导致明显卡顿;关闭 swap 后仍可能先经历回收和延迟,并非只剩 OOM 一种结果。

2.4 PSI 的时间单位是秒

for resource in cpu memory io; do
  file="/proc/pressure/$resource"
  if [ -r "$file" ]; then
    printf '\n[%s]\n' "$resource"
    cat "$file"
  fi
done

示例:

some avg10=12.50 avg60=8.30 avg300=4.20 total=98372103
full avg10=3.50 avg60=1.20 avg300=0.40 total=18372103

avg10/avg60/avg300 是 10、60、300 秒 尺度的压力趋势,单位为百分比;total 为累计微秒。some 表示至少部分任务因该资源停滞,full 表示所有非空闲任务同时停滞。full 的同一时间尺度数值不应大于 some。[3]

PSI 自 Linux 4.20 引入,是否可用取决于内核编译与启动配置。较新内核可显示系统级 CPU full,但该值在系统级未定义、为兼容通常置零,不能用它判断整机 CPU 停滞。I/O PSI 高说明 I/O 压力,不能独自定位到某块本地盘。[3]

2.5 磁盘看延迟、队列与吞吐

iostat 的 r_await/w_await 是已完成读写请求的平均时间,含排队与处理时间;aqu-sz 是平均未完成请求数,并不等于“纯等待队列长度”。它们是块设备层指标,不等于数据库事务或应用请求延迟。[1]

%util 在传统串行设备上可辅助判断繁忙程度,在 NVMe、RAID 和并行存储上不能独自判定容量上限。队列大于零也是正常并行负载的一部分。判断瓶颈要看相同负载下延迟是否偏离基线、队列是否持续积压、吞吐是否触达能力上限,以及业务是否受影响。旧版 svctm 不适合推断真实服务时间。[1]

不要给所有本地盘规定“正常延迟必须 1ms”。介质、请求大小、队列深度、同步写语义和云盘配额都会改变基线。均值正常也不能排除长尾或尚未完成的卡死请求。

3. CPU 方向:从进程到线程

mpstat -P ALL 1 5
pidstat -u 1 5
pidstat -w 1 5
ps -eo pid,pcpu,etime,comm --sort=-pcpu | head -15

先区分单核热点、整机计算负载和内核开销。ps %CPU 是进程生命周期平均值,不能代替 interval 采样;pidstat 不是通用系统调用计数器。

Java 线程定位示例,先把数值替换为真实 PID 与 TID:

APP_PID=12345
HOT_TID=12346
top -H -p "$APP_PID"
TID_HEX=$(printf '%x' "$HOT_TID")
jstack "$APP_PID" | grep -F -A 20 "nid=0x${TID_HEX} "

需在正确 PID namespace 下,使用有权限且与目标兼容的 JDK。线程栈可能引入停顿,先评估影响;必要时多次短采样确认热点。Go 可在已授权的 pprof 入口采样;perf 要控制频率、范围和时长。不能把某个 profiler 的固定开销百分比当作所有环境的保证。

CPU 频率排查可以读取 cpufreq 与驱动状态,但虚拟机的 /proc/cpuinfo MHz 不一定反映有效计算能力。powersave 在某些驱动下并非固定最低频率,performance 也不保证始终最高频率。调 governor 前确认驱动、支持策略、热限制和功耗约束,不提供跨发行版通用的 cpufreq 服务启用命令。

4. 内存方向:区分占用、回收与 OOM

free -h
vmstat 1 10
pidstat -r 1 5
grep -E 'MemAvailable|Dirty|Writeback|Shmem|SReclaimable' /proc/meminfo
grep -E 'pgscan_direct|pgsteal_direct|allocstall|oom_kill' /proc/vmstat
ps -eo pid,user,rss,vsz,etime,comm --sort=-rss | head -15
sudo journalctl -k --since '-2 hours' --no-pager | grep -iE 'oom|killed process'

/proc/vmstat 是累计计数,间隔读取看增量;nr_dirty/nr_writeback 等是状态值,注意具体字段语义。pidstat -r 的主缺页可能来自文件映射读取,不全是 swap。RSS 增长也可能是缓存或业务工作集增长,只有结合生命周期与分配证据才能判断泄漏。

查看进程 swap 占用:

for proc_dir in /proc/[0-9]*; do
  swap_kib=$(awk '/^VmSwap:/{print $2}' "$proc_dir/status" 2>/dev/null)
  if [[ "$swap_kib" =~ ^[0-9]+$ ]] && (( swap_kib > 0 )); then
    printf '%s\t%s\t%s\n' "$swap_kib" "${proc_dir##*/}" \
      "$(cat "$proc_dir/comm" 2>/dev/null)"
  fi
done | sort -rn | head -10

输出是 KiB、PID、进程名;VmSwap 不完整覆盖所有共享内存换页。某进程 swap 大只能说明部分匿名内存被换出,不能证明它当前拖慢整机。

OOM 要区分全局、NUMA/内存策略限制、cgroup 限额与用户空间 OOM 管理器。无内核日志不能直接排除 OOM:权限、日志覆盖、容器视角与日志转发都可能造成缺失。cgroup v2 可检查目标 cgroup 的 memory.events、memory.current 与 memory.max;v1 使用对应版本的统计接口。

5. 磁盘方向:设备、文件系统与等待任务

iostat -y -x 1 5
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS
findmnt -T /var/lib/mysql -o TARGET,SOURCE,FSTYPE,OPTIONS
df -hT /var/lib/mysql
df -i /var/lib/mysql
pidstat -d 1 5
ps -eLo pid,tid,stat,wchan:32,comm | awk '$3 ~ /^D/'

旧版 lsblk 可改用 MOUNTPOINT。pidstat -d 的进程 I/O 记账与设备吞吐不总一一对应,缓存、回写线程、共享 I/O 和网络文件系统均会影响归属。

若任务长时间处于 D 状态,可由有权限的账号读取对应线程内核栈:

APP_PID=12345
TASK_TID=12346
sudo cat "/proc/$APP_PID/task/$TASK_TID/stack"

栈里的 NFS/RPC、文件系统、块层函数是定位线索,不是仅靠一个函数名就能定罪。普通不可中断等待不能被 SIGKILL 立即终止;某些可杀等待可响应致命信号,要按实际等待路径判断。优先恢复存储链路,不能把重启当作唯一答案。

df 高、du 低时检查:删除后仍被进程打开的文件、挂载点下被遮蔽的数据、文件系统元数据与保留空间,以及统计是否跨了不同文件系统。可使用 sudo lsof +L1 辅助定位已删除但仍打开的文件。

ext4 的错误处理策略不代表 XFS 或所有文件系统;确认只读请检查 findmnt 的挂载选项和内核日志。禁止在已挂载的生产文件系统上随意执行修复工具。

6. 网络方向:带宽、重传、队列和 DNS

IFACE=eth0  # 替换为实际网卡,可先执行 ip -br link
sar -n DEV,EDEV 1 5
ip -s link show dev "$IFACE"
sudo ethtool "$IFACE"
sudo ethtool -S "$IFACE"
ss -lnt
nstat -az TcpRetransSegs TcpOutSegs TcpExtListenOverflows TcpExtListenDrops

接口 drop/error 和 TCP 重传是累计计数,要看同一窗口增量。RX dropped 可能来自驱动、过滤或资源不足,不能直接等同于网卡 ring 太小。重传也可能来自拥塞、乱序或对端行为,需结合两端观察。

ss -lnt 的监听行 Recv-Q 是完成握手后等待 accept 的连接数,Send-Q 是 backlog 上限;3/511 并不接近满,510/511 才值得关注,还要结合 ListenOverflows 的增量确认溢出。

sysstat 的网络 kB/s 口径应按本机版本手册确认;换算为链路 bit/s 时注意字节、比特、KiB 与十进制 Mbps 的区别。云带宽配额不一定等于网卡协商速率。

cat /proc/net/softnet_stat
sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
dig +stats example.com

softnet_stat 使用十六进制累计值,第二列是 dropped,第三列是 time_squeeze,后者表示一次处理因预算等限制未能完成,不是丢包数。[4] 调队列数、ring、RPS 或中断分布可能短暂影响链路,不应标为“热生效所以无风险”。

conntrack 模块未启用时 sysctl 项可能不存在。表满要先查条目状态与来源;已正常关闭的连接不会都以 ESTABLISHED 的长超时滞留。调整 TIME_WAIT 超时不能解释或解决所有 ESTABLISHED 条目积压。

外部计时示例:

curl -sS -o /dev/null --connect-timeout 5 --max-time 30 \
  -w 'http=%{http_code} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} first_byte=%{time_starttransfer} total=%{time_total}\n' \
  https://api.example.com/health

这些时间是从请求开始累计的。要估算独立阶段,通常需要相减;首字节时间含 DNS、连接、TLS、上传和服务端等待,不能直接当成应用处理耗时。mtr 中间跳丢包可能是探测限速,末跳丢包也需结合探测方式与真实业务验证。单点 pcap 的 FIN/RST 来源不足以排除代理代发或中间设备注入。

7. 监控与配置:避免把错误固化进脚本

7.1 使用 node_exporter 原生计数推导平均 I/O 时间

node_exporter 的 diskstats collector 通常已有读写完成数和耗时计数,不必依赖固定列号解析 iostat。示例:

# 平均读请求时间,单位秒;无读请求时 NaN,不伪造为零
rate(node_disk_read_time_seconds_total{device="sdb"}[5m])
/
rate(node_disk_reads_completed_total{device="sdb"}[5m])

写请求用 node_disk_write_time_seconds_total / node_disk_writes_completed_total 的 rate 比值;乘 1000 才是毫秒。node_disk_io_time_seconds_total 的 rate 更接近繁忙时间比例,不能替代读写耗时计数。[5]

Load 告警的聚合标签必须一致:

node_load1
/ on(instance, job)
count by(instance, job) (node_cpu_seconds_total{mode="idle"})
> 2

如环境有 cluster 等身份标签,应在匹配与聚合中一起保留。阈值只是起点,需要按机器角色和业务延迟校准。

7.2 sysstat 历史采集以实际 timer/cron 为准

systemctl list-timers --all 'sysstat*'
systemctl cat sysstat-collect.timer

发行版可能使用 cron、systemd timer 或 /etc/default/sysstat 控制采集。历史文件常见于 /var/log/sa 或 /var/log/sysstat;/etc/sysstat/sysstat 不是通用 cron 文件。sar -A 1 1 只证明即时采样可运行,持久采集是否生效要确认计划任务实际执行、历史文件增长且 sar -f 能读取。

低频 sar 的 CPU/I/O 等速率往往是采样间隔平均值,内存等字段可能是采样状态。短毛刺既可能被稀释也可能被漏掉,不能用平稳历史曲线否定用户反馈。

7.3 调 sysctl 必须能恢复运行值

以下演示操作方式,10 不是通用推荐值。确认适用后,在 root shell 执行:

backup_dir=$(mktemp -d /var/tmp/swappiness-change.XXXXXX)
sysctl -n vm.swappiness > "$backup_dir/original-value"
sysctl -w vm.swappiness=10
printf 'vm.swappiness = 10\n' > /etc/sysctl.d/90-example-swappiness.conf
printf '原值记录在 %s\n' "$backup_dir/original-value"

前提是示例配置文件原先不存在;若存在,先另存其内容。回滚时用实际备份目录:

backup_dir=/var/tmp/swappiness-change.ACTUAL
original_value=$(cat "$backup_dir/original-value")
sysctl -w "vm.swappiness=$original_value"
rm -- /etc/sysctl.d/90-example-swappiness.conf
sysctl vm.swappiness

删除配置文件不会自动恢复内核运行值;也不能假设原值一定是 60。现代内核的 swappiness 支持 0–200,含义是 swap 与文件分页的相对成本权衡,需考虑 zram/zswap 和实际存储。[6]

dirty_ratio 的基数是可用于脏页的可用内存范围,并非简单的物理总内存百分比;同一方向的 bytes 与 ratio 配置互斥。调脏页阈值应先检查已有 bytes 值并记录原配置。[6]

8. 日志要与解析命令配套

Nginx 的 log_format 放在 http 中;以下使用 JSON,避免按空格列号误解析:

log_format perf_json escape=json
  '{"ts":"$time_iso8601","status":$status,"uri":"$uri",'
  '"rt":$request_time,"urt":"$upstream_response_time","upstream":"$upstream_addr"}';
access_log /var/log/nginx/perf.json.log perf_json;

校验通过再 reload,生成新请求后查询:

jq -c 'select(.rt > 1)' /var/log/nginx/perf.json.log

$request_time 包含客户端收发与上游等待;$upstream_response_time 也不是纯应用计算时间,多次上游尝试可产生多个值。两者差值只能辅助分析,不能直接命名为“Nginx 自身处理时间”。[7]

9. 示例复盘:内存压力让磁盘忙起来

以下是教学示例,不是已验证的真实事故。

现象:接口延迟上升,同一窗口 MemAvailable 下降、memory PSI 上升、swap in/out 明显增加、系统盘 I/O 变忙。不能直接定性为坏盘。继续查看应用工作集、进程 swap、cgroup 限额和分配记录,判断是否内存增长导致换页。

若确认某应用异常增长,可以先保留必要现场,摘流后按服务手册重启或回滚,再验证相同业务负载下内存曲线、换页速率和 P99 延迟。重启后恢复只说明进程状态可能参与问题,不能独自证明内存泄漏。

10. 验收与操作边界

修复后至少覆盖一次复发周期,并核对业务成功率、P95/P99、资源等待与错误增量。不能要求任何机器“D 状态永远为零”或“少量 swap 必须为零”;应与其正常负载基线比较。

处理生产故障时遵守以下边界:

  • swapoff 可能造成严重压力,available 大于 SwapUsed 也不是安全保证,还需考虑可回收性、并发增长和内存策略。
  • drop_caches 不用于常规释放内存;清理缓存会增加后续读 I/O。
  • 删除正在写的日志可能不释放空间;truncate 会破坏内容,非追加写入还可能产生稀疏文件。优先用服务支持的轮转与重新打开日志流程。
  • 快照、dump、抓包应限制大小与权限;失败必须记录为“未采集”,不能输出虚假的 PASS。
  • 重启、参数修改与批量发布都有影响范围。记录原值、先做单节点变更、验收后再扩大范围。

性能排障的目标,是用相互独立的证据缩小范围,再用可验证的处置检验判断。CPU、内存、磁盘和网络常存在因果关联,任何单指标都不足以完成归因。

参考资料

  1. sysstat:iostat 手册
  2. procps-ng:vmstat 手册
  3. Linux Kernel:PSI
  4. Linux Kernel:softnet_stat 输出源码
  5. node_exporter:Linux diskstats collector
  6. Linux Kernel:VM sysctl
  7. Nginx:日志模块



上一篇:RAD Studio 13.2 IDE导航器体验:Minimap缩略图与Goto快速跳转
下一篇:while(!stop) 死循环的真相:GCC -O2 优化器在编译期就删掉了读内存
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-9 05:01 , Processed in 0.097417 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表