PHP-FPM占用CPU过高分析及OPcache解决

平时站点CPU使用率都在10%以内,最近发现达到了50%左右
top查看服务器资源使用情况:

%Cpu0  : 41.9 us,  1.3 sy,  0.0 ni, 56.1 id,  0.3 wa,  0.0 hi,  0.3 si,  0.0 st
KiB Mem :  1016164 total,    68120 free,   583512 used,   364532 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   251176 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 5705 www       20   0  281272  40360   4516 S 15.0  4.0   0:09.09 php-fpm
 5704 www       20   0  281292  40604   4584 S  8.0  4.0   0:08.89 php-fpm
 5706 www       20   0  281124  41124   5296 S  8.0  4.0   0:08.80 php-fpm
 5707 www       20   0  281328  40596   4580 S  7.7  4.0   0:08.77 php-fpm

分析top的结果,我们可以看出是FPM占用了过多的CPU资源。那么问题就清晰了:FPM为什么占用了这么多CPU资源,如何分析,如何解决?

带着这些问题,思考&解决思路整理如下:
0、查看nginx error和php-fpm slowlog, 排除应用本身的明显异常问题。

1、查看PHP是否开启OPcache缓存。
OPcache通过将PHP脚本预编译的字节码存储到共享内存中来提升PHP的性能,存储预编译字节码的好处就是省去了每次加载和解析PHP脚本的开销。

在phpinfo中或者php.ini中查看是否开启了OPcache,如果没有开启之。
官方建议php.ini中关于opcache的设置如下,更多参数参考这里

[opcache]
zend_extension=/usr/local/php/lib/php/extensions/no-debug-zts-20170718/opcache.so
; OPcache打开/关闭开关。当设置为Off或者0时,会关闭Opcache, 代码没有被优化和缓存。
opcache.enable=1
; OPcache共享内存存储大小。用于存储预编译的opcode(以MB为单位)。
opcache.memory_consumption=64
; 用来存储临时字符串的内存大小,以兆字节为单位.
opcache.interned_strings_buffer=8
; 这个选项用于控制内存中最多可以缓存多少个PHP文件。
opcache.max_accelerated_files=4000
; 从缓存不被访问后,等待多久后(单位为秒)调度重启.
opcache.force_restart_timeout=180
; 这个选项用于设置缓存的过期时间(单位是秒),当这个时间达到后,opcache会检查你的代码是否改变,如果改变了PHP会重新编译它,生成新的opcode,并且更新缓存。
opcache.revalidate_freq=60
;如果启用,则会使用快速停止续发事件。 所谓快速停止续发事件是指依赖 Zend 引擎的内存管理模块 一次释放全部请求变量的内存,而不是依次释放每一个已分配的内存块。
opcache.fast_shutdown=1
; CLI环境下,PHP启用OPcache。这主要是为了测试和调试。从 PHP 7.1.2 开始,默认启用。
opcache.enable_cli=1

生产环境实际值参考(6C CPU,16G内存,300G硬盘):

opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=1
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.save_comments=0
opcache.enable_file_override=1


我这里的原因就是没有开启Opcache,开启后的top结果如下,可以看到效果很明显:

%Cpu(s):  7.7 us,  0.3 sy,  0.0 ni, 91.3 id,  0.7 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1016164 total,   160036 free,   498056 used,   358072 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   317092 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 5815 www       20   0  328876  34644  19648 S  2.3  3.4   0:00.59 php-fpm
 5816 www       20   0  328856  29796  14644 S  1.7  2.9   0:00.46 php-fpm
 5817 www       20   0  328856  27304  13188 S  1.3  2.7   0:00.45 php-fpm
 5818 www       20   0  328856  36976  22816 S  1.3  3.6   0:00.93 php-fpm

2、strace工具调试一下看瓶颈在哪里。(strace一篇比较友好的入门介绍参考这里)
比如使用某些框架,需要动态的打开文件,框架是否支持优化的方案,到框架的官网上去看下,一般都有,另外框架一般默认加载了大而全的东西,检查下如果不是需要的是否可不加载。

[root@izj6cfhaw27k49x8usszs3z ~]# strace -c -p 5818
strace: Process 5818 attached
^Cstrace: Process 5818 detached
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 33.14    0.000056           2        24           sendto
 28.40    0.000048           1        50           poll
 17.75    0.000030           1        50           recvfrom
  9.47    0.000016           8         2           setsockopt
  6.51    0.000011           0        78           read
  1.78    0.000003           0        11        10 lstat
  0.59    0.000001           0         5           open
  0.59    0.000001           0        11           close
  0.59    0.000001           0        31           access
  0.59    0.000001           0         8           getdents
  0.59    0.000001           0         4           openat
  0.00    0.000000           0         1           write
  0.00    0.000000           0        12           stat
  0.00    0.000000           0         5           fstat
  0.00    0.000000           0        13           lseek
  0.00    0.000000           0         8           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0         4           setitimer
  0.00    0.000000           0         1           socket
  0.00    0.000000           0         1         1 connect
  0.00    0.000000           0         1           accept
  0.00    0.000000           0         1           shutdown
  0.00    0.000000           0         1           getsockopt
  0.00    0.000000           0         5           fcntl
  0.00    0.000000           0         2           times
------ ----------- ----------- --------- --------- ----------------
100.00    0.000169                   330        11 total

从上图可以看到耗时最长的是sendto操作,可以单独跟踪下sendto操作

[root@izj6cfhaw27k49x8usszs3z ~]# strace -T -e sendto -p 5818
strace: Process 5818 attached
sendto(5, "S\0\0\1\215\242\n\0\0\0\0\300!\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 87, MSG_DONTWAIT, NULL, 0) = 87 <0.000017>
sendto(5, "\22\0\0\0\3SET NAMES utf8mb4", 22, MSG_DONTWAIT, NULL, 0) = 22 <0.000011>
sendto(5, "5\0\0\0\3SET NAMES 'utf8mb4' COLLATE"..., 57, MSG_DONTWAIT, NULL, 0) = 57 <0.000010>
sendto(5, "\32\0\0\0\3SELECT @@SESSION.sql_mode", 30, MSG_DONTWAIT, NULL, 0) = 30 <0.000011>
sendto(5, ".\0\0\0\3SET SESSION sql_mode='NO_EN"..., 50, MSG_DONTWAIT, NULL, 0) = 50 <0.000009>
sendto(5, "\6\0\0\0\2niliu", 10, MSG_DONTWAIT, NULL, 0) = 10 <0.000011>
sendto(5, "H\0\0\0\3SELECT option_name, option_"..., 76, MSG_DONTWAIT, NULL, 0) = 76 <0.000018>
sendto(5, "Y\0\0\0\3SELECT option_value FROM wp"..., 93, MSG_DONTWAIT, NULL, 0) = 93 <0.000025>
sendto(5, "N\0\0\0\3SELECT option_value FROM wp"..., 82, MSG_DONTWAIT, NULL, 0) = 82 <0.000022>

可以看到没有很明显的异常

3、尝试优化业务,DB之前尽量增加Cache。

参考:

《strace 跟踪进程中的系统调用》
通过Strace定位故障原因
Opcode是啥以及如何使用好Opcache
PHP加速器之opcache配置详解

发表评论

电子邮件地址不会被公开。 必填项已用*标注