PHP-FPM进程数不够怎么办
PHP-FPM进程数不够怎么办
这篇文章给大家分享的是有关PHP-FPM进程数不够怎么办的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。
感觉PHP-FPM进程数不够?
作为一个 phper,用的最多的架构就是 LNMP。每次一到流量来了,我们的服务就从原来的 几百毫秒到几秒的时间。这个时候我们各种猜测,mysql 有慢 sql,redis 有大 key,php-fpm 进程数不够等等情况。其中可以通过业务的一些日志来排查如上情况。我们这次主要证明的却是 php-fpm 进程数不够情况的实践。
重现现场
1.将我本地的的 PHP-FPM 进程数调整为 2
#vim /etc/php-fpm.d/www.conf pm = static pm.max_children = 2
2.使用 ab 来压测接口
$ ab -c 40 -n 3000 http://127.0.0.1/group/check_groups Server Software: nginx/1.16.0 Server Hostname: miner_platform.cn Server Port: 80 Document Path: /group/check_groups Document Length: 44 bytes Concurrency Level: 40 Time taken for tests: 29.384 seconds Complete requests: 3000 Failed requests: 0 Write errors: 0 Total transferred: 699000 bytes HTML transferred: 132000 bytes Requests per second: 102.10 [#/sec] (mean) Time per request: 391.788 [ms] (mean) Time per request: 9.795 [ms] (mean, across all concurrent requests) Transfer rate: 23.23 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.2 0 3 Processing: 306 344 80.6 318 3558 Waiting: 306 343 80.5 318 3555 Total: 307 344 80.6 318 3558 Percentage of the requests served within a certain time (ms) 50% 318 66% 322 75% 333 80% 369 90% 428 95% 461 98% 508 99% 553 100% 3558 (longest request)
尝试解决问题
1. PHP-FPM STATUS
我们发现接口 318ms 到 3.558s 的都有,那我们如何知道 php-fpm 进程少不够导致这个问题呢?换一种说话有什么办法能让我们知道 php-fpm 内部是处理不过来吗? 这个时候我们就需要打开 php-fpm 内置 status 了。
详细步骤参考:https://www.gaofangfuwuqi.com/php-weizijiaocheng-485633.html
$ curl http://127.0.0.1/status.php pool: www process manager: static start time: 29/Nov/2021:18:27:38 +0800 start since: 6493 accepted conn: 3136 listen queue: 38 max listen queue: 39 listen queue len: 128 idle processes: 0 active processes: 2 total processes: 2 max active processes: 2 max children reached: 0 slow requests: 0
具体详细的字段可以参见上面的链接,有详细说明,我们主要说下几个参数
-
listen queue:这个就是此时此刻我们的 php-fpm 作为服务端,处于 accept 队列 的数量。
-
max listen queue: 从 php-fpm 进程启动到现在处于等待连接的最大数量(说白了,就是我们上面说的 listen queue 的最大值持久化)
-
listen queue len : 有过 socket 网络编程经验的同学都知道。int listen(int sockfd, int backlog); 是可以设置该参数,但是他和系统设置有关系。
2. netstat 查看链接状态
我们得到的结论是:当 php-fpm 进程处理不过来的时候,请求就会放在 accept 队列,知道了这个情况以后,我们甚至不需要通过 status。
第一行表示的监听 socket, Recv-Q 表示 accept queue 长度。
$netstat -antp | grep php-fpm tcp 38 0 127.0.0.1:9000 0.0.0.0:* LISTEN 97/php-fpm: master tcp 8 0 127.0.0.1:9000 127.0.0.1:55540 ESTABLISHED 964/php-fpm: pool w tcp 8 0 127.0.0.1:9000 127.0.0.1:55536 ESTABLISHED 965/php-fpm: pool w
综上我们知道了,当 PHP-FPM 进程数不够的时候,nginx 客户端请求的连接的 accept 队列 长度就会变大。这样就完了吗?不,我们还需要去分析为什么能得到这个现象。
原理分析
简述 PHP-FPM 工作过程
首先我们需要简单里说一说 php-fpm 的工作过程。我们就简单模型一下它的伪代码(这里只为了表述整个 socket 的过程)
// 1. 创建 socket $socket = socket_create(AF_INET, SOCK_STREAM, 0); // 2. 绑定socket socket_bind($socket, "0.0.0.0", 9000); // 3. 监听 socket socket_listen($socket, 5); for($i=0;$i<2;$i++) { $pid = pcntl_fork() // 4. 创建2个进程 if ($pid == 0) { // 5. 子进程接受socket while($fd = socket_accept($socket)) { echo "客户端${fd}连接" . PHP_EOL; $tmp = socket_read($fd, 1024); echo "client data:" . $tmp . PHP_EOL; $data = "HTTP/1.1 200 okrnContent-Length:2rnrnhi"; socket_write($fd, $data, strlen($data)); } exit; } } // 5. 监听子进程退出 // 其他 TODO
1.master 进程创建了监听 socket,但是不处理业务正在
2.work 进程接受同步堵塞接受请求(堵塞在 accept),然后处理业务。
抓取 nginx->php-fpm socket
我们知道了 php-fpm 大概工作的过程,这个时候我们就需要通过一次请求大概知道 nginx 与 php-fpm 交互的过程。
$curl http://miner_platform.cn/group/check_groups {"code":10006,"message":"signu65e0u6548."}
1.nginx 系统调用
需要关注的点都在这个里面注释了。抓取的是 nginx work 进程
$ strace -f -s 64400 -p 958 strace: Process 958 attached epoll_wait(8, [{EPOLLIN, {u32=1226150064, u64=94773974503600}}], 512, -1) = 1 accept4(6, {sa_family=AF_INET, sin_port=htons(46616), sin_addr=inet_addr("127.0.0.1")}, [112->16], SOCK_NONBLOCK) = 3 epoll_ctl(8, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=1226159737, u64=94773974513273}}) = 0 epoll_wait(8, [{EPOLLIN, {u32=1226159737, u64=94773974513273}}], 512, 60000) = 1 recvfrom(3, "GET /group/check_groups HTTP/1.1rnUser-Agent: curl/7.29.0rnHost: miner_platform.cnrnAccept: */*rnrn", 1024, 0, NULL, NULL) = 99 stat("/data/miner_platform/src/public/group/check_groups", 0x7ffcb593d1b0) = -1 ENOENT (No such file or directory) stat("/data/miner_platform/src/public/group/check_groups", 0x7ffcb593d1b0) = -1 ENOENT (No such file or directory) epoll_ctl(8, EPOLL_CTL_MOD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=1226159737, u64=94773974513273}}) = 0 lstat("/data", {st_mode=S_IFDIR|0777, st_size=4096, ...}) = 0 lstat("/data/miner_platform", {st_mode=S_IFDIR|0777, st_size=4096, ...}) = 0 lstat("/data/miner_platform/src", {st_mode=S_IFDIR|0777, st_size=4096, ...}) = 0 lstat("/data/miner_platform/src/public", {st_mode=S_IFDIR|0777, st_size=4096, ...}) = 0 getsockname(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("127.0.0.1")}, [112->16]) = 0 // 1. 创建 socket socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 11 ioctl(11, FIONBIO, [1]) = 0 epoll_ctl(8, EPOLL_CTL_ADD, 11, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=1226163953, u64=94773974517489}}) = 0 // 2. 连接 127.0.0.1:9000 connect(11, {sa_family=AF_INET, sin_port=htons(9000), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) epoll_wait(8, [{EPOLLOUT, {u32=1226159737, u64=94773974513273}}, {EPOLLOUT, {u32=1226163953, u64=94773974517489}}], 512, 60000) = 2 getsockopt(11, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 // 3. 按照FASTCGI协议写入这次请求 writev(11, [{iov_base="11 1 10 1 14 12!7 17)SCRIPT_FILENAME/data/miner_platform/src/public/index.phpf QUERY_STRING163REQUEST_METHODGETf CONTENT_TYPE16 CONTENT_LENGTHvnSCRIPT_NAME/index.phpv23REQUEST_URI/group/check_groupsfnDOCUMENT_URI/index.phpr37DOCUMENT_ROOT/data/miner_platform/src/public1710SERVER_PROTOCOLHTTP/1.1164REQUEST_SCHEMEhttp217GATEWAY_INTERFACECGI/1.117fSERVER_SOFTWAREnginx/1.16.0vtREMOTE_ADDR127.0.0.1v5REMOTE_PORT46616vtSERVER_ADDR127.0.0.1v2SERVER_PORT80v21SERVER_NAMEminer_platform.cn173REDIRECT_STATUS20017vHTTP_USER_AGENTcurl/7.29.0t21HTTP_HOSTminer_platform.cnv3HTTP_ACCEPT*/* 14 1 15 1 ", iov_len=592}], 1) = 592 epoll_wait(8, [{EPOLLIN|EPOLLOUT, {u32=1226163953, u64=94773974517489}}], 512, 60000) = 1 // 4. 接受 PHP-FPM响应结果 recvfrom(11, "16 1 2571 X-Powered-By: PHP/7.2.16rnCache-Control: no-cache, privaternDate: Wed, 01 Dec 2021 12:24:52 GMTrnContent-Type: application/jsonrnrn{"code":10006,"message":"sign\u65e0\u6548."} 13 1 10 "} ", 4096, 0, NULL, NULL) = 200 epoll_wait(8, [{EPOLLIN|EPOLLOUT|EPOLLRDHUP, {u32=1226163953, u64=94773974517489}}], 512, 60000) = 1 readv(11, [{iov_base="", iov_len=3896}], 1) = 0 // 5. 关闭这次socket连接 close(11) = 0 // 6. 响应给浏览器 writev(3, [{iov_base="HTTP/1.1 200 OKrnServer: nginx/1.16.0rnContent-Type: application/jsonrnTransfer-Encoding: chunkedrnConnection: keep-alivernX-Powered-By: PHP/7.2.16rnCache-Control: no-cache, privaternDate: Wed, 01 Dec 2021 12:24:52 GMTrnrn", iov_len=222}, {iov_base="2crn", iov_len=4}, {iov_base="{"code":10006,"message":"sign\u65e0\u6548."}", iov_len=44}, {iov_base="rn", iov_len=2}, {iov_base="0rnrn", iov_len=5}], 5) = 277 write(5, "127.0.0.1 - - [01/Dec/2021:20:24:52 +0800] "GET /group/check_groups HTTP/1.1" 200 55 "-" "curl/7.29.0" "-" 1.029 127.0.0.1:9000 200 1.030n", 138) = 138 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 epoll_wait(8, [{EPOLLIN|EPOLLOUT|EPOLLRDHUP, {u32=1226159737, u64=94773974513273}}], 512, 65000) = 1 recvfrom(3, "", 1024, 0, NULL, NULL) = 0 close(3) = 0 epoll_wait(8,
2.php-fpm 系统调用
抓取了 php-fpm work 进程
// 1. accept 接收到了 nginx(127.0.0.1:45512 ) 客户端发送的数据 965 accept(9, {sa_family=AF_INET, sin_port=htons(45512), sin_addr=inet_addr("127.0.0.1")}, [112->16]) = 4 中间省略了许多 // 2. 响应给客户端 965 write(4, "16 1 2571 X-Powered-By: PHP/7.2.16rnCache-Control: no-cache, privaternDate: Wed, 01 Dec 2021 12:37:18 GMTrnContent-Type: application/jsonrnrn{"code":10006,"message":"sign\u65e0\u6548."} 13 1 10 p ", 200) = 200 // 3. 不给给这个socket 写数据了 965 shutdown(4, SHUT_WR) = 0 // 4. 接受nginx(127.0.0.1:45512 )客户端数据 965 recvfrom(4, "15 1 ", 8, 0, NULL, NULL) = 8 // 5. 接受nginx(127.0.0.1:45512 )客户端数据 965 recvfrom(4, "", 8, 0, NULL, NULL) = 0 // 6. 关闭这个连接 965 close(4) = 0 965 lstat("/data/miner_platform/src/vendor/composer/../../app/Http/Middleware/BusinessHeaderCheck.php", {st_mode=S_IFREG|0777, st_size=989, ...}) = 0 965 stat("/data/miner_platform/src/app/Http/Middleware/BusinessHeaderCheck.php", {st_mode=S_IFREG|0777, st_size=989, ...}) = 0 965 chdir("/") = 0 965 times({tms_utime=3583, tms_stime=1977, tms_cutime=0, tms_cstime=0}) = 4315309933 965 setitimer(ITIMER_PROF, {it_interval={tv_sec=0, tv_usec=0}, it_value={tv_sec=0, tv_usec=0}}, NULL) = 0 965 fcntl(3, F_SETLK, {l_type=F_UNLCK, l_whence=SEEK_SET, l_start=0, l_len=0}) = 0 965 setitimer(ITIMER_PROF, {it_interval={tv_sec=0, tv_usec=0}, it_value={tv_sec=0, tv_usec=0}}, NULL) = 0 965 accept(9,
TCP 三次握手
上面我们已经清楚了一次请求,请求并发高的时候流程也是如此,这个时候我们就引出了下面这个图与我们上面描述的过程是一样的,只是细化了三次握手的过程。这个时候我们引出了 sync queue 和 accept queue。
-
我们调用 listen (上面是 php-fpm master 进程执行的),于此同时内核创建了两个队列 sync queue 和 accept queue
-
三次握手第二步当 Server (指的是 php-fpm master 进程) 发送了 SYN+ACK 报文后,此时会将这个信息放入到 sync queue
-
当三次握手完成时,未被应用 (指的是 php-fpm work 进程) 调用 accept 取走的连接队列。此时的 socket 处于 ESTABLISHED 状态。每次应用调用 accept() 函数会移除队列头的连接。如果队列为空,accept () 通常会阻塞。全连接队列也被称为 accept queue。
感谢各位的阅读!关于“PHP-FPM进程数不够怎么办”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!
[微信提示:高防服务器能助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。
[图文来源于网络,不代表本站立场,如有侵权,请联系高防服务器网删除]
[