记录一次 CPU sy使用过高的排查经历

机器大概:这台机器是跑 PHP 的 4C 统计机器,同时可能会有多个进程在进行。

使用 Top 观察如下

%Cpu(s): 46.7 us, 35.6 sy,  0.0 ni, 17.7 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st

有一个很明显的现象 sy 占用很高,通常这个值要比较小才好。
Sy 定义: system : time running kernel processes。表示CPU在执行内核代码占用的cpu时间。

查了下相关的文章,主要都是说上下文切换导致的。原文如下:
如果可运行的线程数大于CPU的数量,那么OS最终会强行换出正在执行的线程,从而使其他线程能够使用CPU。这会引起上下文切换,它会保存当前运行线程的执行上下文,并重建新调入线程的执行上下文。切换上下文是要付出代价的;线程的调度需要操控OS和JVM中共享的数据结构。你的程序与OS、JVM使用相同的CPU;CPU在JVM和OS的代码花费越多时间,意味着用于你的程序的时间就越少。但是JVM和OS活动的花费并不是切换上下文开销的唯一来源。当一个新的线程被换入后,它所需要的数据可能不在当前处理器本地的缓存中,所以切换上下文会引起缓存缺失的小恐慌,因此线程在第一次调度的时候会运行得稍慢一些。即使有很多其他正在等待的线程,调度程序也会为每一个可运行的线程分配一个最小执行时间的定额。就是因为这个原因:它分期偿付切换上下文的开销,获得更多不中断的执行时间,从整体上提高了吞吐量 (以损失响应性为代价)

从上面的回答中提炼了两点:频繁上下文切换和缓存

频繁上下文切换
如何证明这些进程进行频繁的上下午切换?这些进程导致 CPU sy高时间很短,所以我想怎么看这种瞬间的任务。
然后想到一个工具 sysrq ,它可以观察当前的内存快照、任务快照,可以构造 vmcore 把系统的所有信息都保存下来,甚至还可以在内存紧张的时候用它杀掉内存开销最大的那个进程。
具体使用

  1. 设置系统4变量 sysctl -w kernel.sysrq=1
  2. 输出到内核缓冲区 echo t > /proc/sysrq-trigger
  3. 使用 dmesg 查看快照信息
    在 cpu sy高的时候执行步骤 2 即可,但是有时候太快了。单身二十几年的手速都来不及,所以写了一个shell 采集。
 #!/bin/sh
while [ 1 ]; do
  top -bn2 grep "Cpu(s)" tail -1 awk '{
     # $2 is usr, $4 is sys.
     # 这个规则可以看自己系统情况调节
     if ($2 < 10.0 && $4 > 10.0) {
          # 日志文件名生成规则
          while ("date" getline date) {
                split(date, str, " ");
                prefix=sprintf("%s_%s_%s_%s", str[2],str[3], str[4], str[5]);
          }
          # 目录自己调整
          sys_usr_file=sprintf("/work/tmp/%s_info.highsys", prefix);
          print $2 > sys_usr_file;
          print $4 >> sys_usr_file;

          # run sysrq
          system("echo t > /proc/sysrq-trigger");
     }
 }'
 
 # 这个时间也可以自己调节
 sleep 5s
done

使用 demsg 查看上面输出的快照数据,有些特别的发现。每次执行PHP时候一定会发生下面调用信息:

[73304.409408] php             W ffff88023252fba4     0 12374  12369 0x00000080
[73304.409410]  ffff88023252fa30 0000000000000086 ffff880232a49700 ffff88023252ffd8
[73304.409412]  ffff88023252ffd8 ffff88023252ffd8 ffff880232a49700 ffff88023252fb88
[73304.409414]  00000000039386ff 0000000000000000 0000000000000000 ffff88023252fba4
[73304.409416] Call Trace:
[73304.409418]  [<ffffffff8163a909>] schedule+0x29/0x70
[73304.409420]  [<ffffffff81639a22>] schedule_hrtimeout_range_clock+0xb2/0x150
[73304.409422]  [<ffffffff810a95c0>] ? hrtimer_get_res+0x50/0x50
[73304.409424]  [<ffffffff81639a16>] ? schedule_hrtimeout_range_clock+0xa6/0x150
[73304.409426]  [<ffffffff81639ad3>] schedule_hrtimeout_range+0x13/0x20
[73304.409428]  [<ffffffff811f2bb5>] poll_schedule_timeout+0x55/0xb0
[73304.409430]  [<ffffffff811f413d>] do_sys_poll+0x4cd/0x580
[73304.409433]  [<ffffffff8163cb5b>] ? _raw_spin_unlock_bh+0x1b/0x40
[73304.409435]  [<ffffffff815154a8>] ? release_sock+0x118/0x170
[73304.409437]  [<ffffffff815769e6>] ? tcp_sendmsg+0xd6/0xc20
[73304.409439]  [<ffffffff811f2ad0>] ? poll_select_copy_remaining+0x150/0x150
[73304.409450]  [<ffffffffa01eed4c>] ? xfs_iunlock+0x11c/0x130 [xfs]
[73304.409452]  [<ffffffff8116902b>] ? unlock_page+0x2b/0x30
[73304.409454]  [<ffffffff81192eb1>] ? __do_fault+0x401/0x510
[73304.409457]  [<ffffffff81197088>] ? handle_mm_fault+0x5b8/0xf50
[73304.409459]  [<ffffffff81511281>] ? SYSC_sendto+0x121/0x1c0
[73304.409460]  [<ffffffff81640ec9>] ? __do_page_fault+0x1f9/0x420
[73304.409463]  [<ffffffff81058aaf>] ? kvm_clock_get_cycles+0x1f/0x30
[73304.409465]  [<ffffffff810d814c>] ? ktime_get_ts64+0x4c/0xf0
[73304.409467]  [<ffffffff811f42f4>] SyS_poll+0x74/0x110
[73304.409469]  [<ffffffff81645909>] system_call_fastpath+0x16/0x1b

出现的关键信息解释:
schedule_hrtimeout 使得当前进程休眠指定的时间,使用CLOCK_MONOTONIC计时系统;
schedule_hrtimeout_range 使得当前进程休眠指定的时间范围,使用CLOCK_MONOTONIC计时系统;
schedule_hrtimeout_range_clock 使得当前进程休眠指定的时间范围,可以自行指定计时系统;

证明了上面的猜想 频繁上下文切换。

缓存
主要是在想上面的 “数据可能不在当前处理器本地的缓存中” 导致频繁的页复制。然后 cpu 瞬间飙升。所以要证明这一点还得继续看上面的日志,果不其然发现了一些可用的信息。

[73304.409471] php             R  running task        0 12375  12371 0x00000080
[73304.409473]  ffff8802328e7f58 0000000000000082 ffff880232a4e780 ffff8802328e7fd8
[73304.409475]  ffff8802328e7fd8 ffff8802328e7fd8 ffff880232a4e780 0000000000000000
[73304.409477]  00007ffdf3ab7c98 00000000000000c8 00007ffdf3ab7aa2 0000000000000093
[73304.409479] Call Trace:
[73304.409481]  [<ffffffff8163b931>] schedule_user+0x31/0xc0
[73304.409483]  [<ffffffff8163d196>] retint_careful+0x14/0x32
[73304.409485] php             R  running task        0 12376  12373 0x00000080
[73304.409486]  0000000000000001 0000000000000000 ffff8802339aff68 ffffffff8119cc61
[73304.409488]  ffff88008ec91800 ffff88008ec91a48 ffff88008ec91800 ffff8802339aff48
[73304.409490]  ffffffff816408d9 0000000000000000 0000000000000001 ffffffff8163d438
[73304.409492] Call Trace:
[73304.409494]  [<ffffffff816411b3>] ? trace_do_page_fault+0x43/0x110
[73304.409496]  [<ffffffff816408d9>] do_async_page_fault+0x29/0xe0
[73304.409498]  [<ffffffff8163d438>] async_page_fault+0x28/0x30

程序运行时候频繁的出现缺页异常(虽然是正常的),同时机智想到一个问题。如果内存页越大,那瞬间 cpu 的负载一定会越大。那开启了内存大页机制不就更加凉凉???

果然这台机器是开启的!!!

$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

总结
频繁上下文切换很大部分是程序开启的进程或线程远远超过机器 CPU 本身的核心数,检查了业务代码,的确是用了 PHP Fock多个子进程。机器本身也开启了内存大页机制,导致瞬间的cpu负载过高从而不断触发报警。

这种情况我的处理办法, 第一 优化代码逻辑。 第二 关闭内存大页机制,内存大页虽好但是不当使用反而是一个负优化。

猜你喜欢

转载自blog.csdn.net/m0_51504545/article/details/112114144