- 書: 解耦PHP——清淨六角之構,適應框架之長存
- 亦余著: 思乎Go (二書之序) — Go程式之全覽 + Go中六角之構
- 吾之項目: 赫爾墨斯IDE |GitHub — 一款供开发者使用之IDE,可配合Claude Code及其他AI编程工具。
- 吾言: xgabriel.com | GitHub
汝之Laravel应用,负重则缓。CPU恒居30%,内存无碍。数据库未觉疲。然p99于周朝之晨仍骤升至8秒。
尔启Grafana。尔察询。尔察缓存之击率。尔增Redis节点。无有变改。
其弊非尔之码。乃php-fpm。详言之,www.conf中五行,载有值,乃为Raspberry Pi所择。
六十秒之池模型
php-fpm者,运行工作进程之池也。网请求至Nginx,Nginx转而经FastCGI至池,一工者取之,运行汝之PHP码,复响应而还,乃待次请求。
其要义在:工者几何,何时而生,何时而复。
汝择三者之一于www.conf:
; /etc/php/8.4/fpm/pool.d/www.conf
pm = dynamic
-
pm = static:固定工作者数量,常存不灭。内存可预知。无生成延迟。流量低谷时耗损RAM。 -
pm = dynamic:设最小最大范围,依需求伸缩。多发行版默认此法。边界处有生成延迟。 -
pm = ondemand:请求至则生工作者,闲置超时则灭。内存最低。冷请求增5-50毫秒。
为专一用途之API服务器,应真通流,static几为正道。骤发之际,生成本为用户所受最劣迟滞。后当复论此。
设一:pm.max_children(众团队多误之)
此乃上限之数。言此箱中可同时运行几PHP之进程。若设之过低,则请求积压;若设之过高,则内核将杀汝之工作者及Redis之副车。
诸发行版之文皆略此公式:
pm.max_children = (total_RAM - reserved_for_OS_and_sidecars) / avg_worker_memory
汝需测量而非臆度之二值:
- 工作者内存之平均:久居之工,经手数事,其实用之 RSS 也,非初启之值。工者渐长,随自载之器载类,缓存渐热,Laravel 濡养容器而增。须待实热之后,乃可量之。
# settled RSS per worker, in MB
ps -ylC php-fpm8.4 --sort:rss \
| awk 'NR>1 { sum+=$8; n++ } END { printf "avg=%.1f MB\n", sum/n/1024 }'
素之Laravel十二工者,常五十至八十兆。二百途之巨构,兼Telescope与数Filament之资,则止百二十至百八十兆。工司繁象之变,可瞬息逾四百兆。
- 预存之RAM:其操作系统本身,Nginx,Redis若在本地,OPcache共享内存(默认128MB),若有APM代理。于4GB之机,通常需预留700MB至1GB,待php-fpm分得一杯羹。
:例证。c5.large EC2:4GB内存,2个vCPU,Ubuntu,Nginx+php-fpm+本地Redis。
- :总计:4096MB
- :预留(操作系统+Nginx+Redis+OPcache+缓冲):约900MB
- 可备 php-fpm:约三千二百MB
- 测得平均工作进程RSS:百四十MB
-
pm.max_children = 3200 / 140 = ~22
pm = static
pm.max_children = 22
此箱常见之形:有人寄之pm.max_children = 50 盖因某Stack Overflow之答,载于二〇一四年云。交通至,其二十三工推之入换页,迟滞陡升,而值班工程师增工,以为可助。非也。反甚。
CPU之侧验。 若汝之平均请求,系于CPU而短(不及五十毫秒),则增工无益。vCPU * 2-4,彼辈徒然鞭挞调度之器。于c5.large,若受制于CPU,则工作者最多四至八人。择其较小者,RAM抑或CPU是也。
二之设:pm.max_requests(内存漏失之保险)
PHP无C之漏失,然php-fpm 工者实自增。乐府自载,缓存之器,静置之盒,己之码中偶成之静。五十万请之後,初起八十兆之工,或至二百二十兆而不复。
pm.max_requests 乃于N请之後更工。其去也清,php-fpm生新易之。
pm.max_requests = 500
值之宜者,高则回收之费不可见,低则漏泄之患不须忧。凡应用之属,多在五百至千之数。库中之码,若分配之甚厉(如生成PDF、图像之处理、具缓存层之HTTP客户端),则宜取其下限。纤微之API工作者,可运行于二千以上。
然有险处:pm.max_requests = 0(部分发行版之默认设置)者,谓工作者永不复用。若汝设为0而未尝显设,则内存增容,汝未知也。宜设之。纵2000亦胜于0。
设三:request_terminate_timeout(实发之超时)
此乃胜出之超时。
PHP有max_execution_time在php.iniNginx有fastcgi_read_timeout在其服务器区块中。php-fpm有request_terminate_timeout在www.conf有三时之限,皆可绝同请,而最短者胜。
request_terminate_timeout = 30s
何故失序:慢查询耗六十秒。fastcgi_read_timeout乃六十年耳。max_execution_time 者三十秒而居 sleep() 之中,或为阻塞之套接字调用,故不能发(PHP惟于指令边界检计时器)。请求使工作者悬停六十秒。复行二十二次,则全池皆陷于同一破败之源。
request_terminate_timeout 乃由 php-fpm 之主以信号施之,故无论工作者何为,皆能发。此乃惟一可确实释出陷顿工作者之超时。
悉设三。存之。request_terminate_timeout略高於max_execution_time故PHP得机以掷之Maximum execution time exceeded且于主核轰击工核之前,录栈踪于日志。
; www.conf
request_terminate_timeout = 35s
; php.ini
max_execution_time = 30
# Nginx server block
fastcgi_read_timeout = 40s;
其序:PHP投掷于卅秒(汝得栈踪),php-fpm毙于卅五秒(工者释),Nginx闭连于四十分(请归五百四)。各层皆前一层之失所倚。
设四:pm.process_idle_timeout(唯于需时相关)
若在pm = dynamic或pm = static,可略此节。乃虚设耳。
为pm = ondemand设,工者应首请而生,无所为则pm.process_idle_timeout秒而亡。
pm = ondemand
pm.max_children = 22
pm.process_idle_timeout = 60s
pm.max_requests = 500
所陷:时过短焉。process_idle_timeout,其扰生灭,乃php-fpm之费事至极。六十息之限,于稀疏之务,诚宜以释内存于静时。下及十息,几无当者。
,其时实用ondemand。 一箱之中设五十池,多者沉寂。有闲役者应突涌之时序。有试场之境,内存重于迟滞。
永勿用 ondemand。 任何面向用户且具实际流量的应用。工作者(自动加载器、操作缓存水合、若非使用Octane则需框架初始化)的诞生成本为50至200毫秒。此乃PHP代码运行单行前所耗尽之全部延迟预算。static 若以更严密的pm.max_children为代价,则此权衡更优。
第五设置:listen.backlog(那无人触碰的SYN队列旋钮)
此乃众人所未知,而决交通高峰时君之行止者也.
listen.backlog此乃套接字接受队列之巨细。当诸役者皆忙,而Nginx欲移他请时,此连接坐于队列中,待役者得闲.
listen = /run/php/php8.4-fpm.sock
listen.backlog = 4096
諸多分發系統之預設值為511。於能容22並行工作者處理50毫秒請求之機器,滿載突發時可將511排隊連接約1.2秒內消弭。其後,新連接即遭拒絕,以connect() failed (11: Resource temporarily unavailable)。
汝已見此於Nginx之記錄。似若php-fpm崩潰。實非。乃排隊溢出也。
將listen.backlog增至4096或8192:
listen.backlog = 8192
核之顶,噬之。 listen.backlog默然限于此与核之小者。net.core.somaxconn于旧时Linux之系统somaxconn默认为128。汝可设之。listen.backlog = 65535在www.conf而实际队列之大小,乃128也。
核值验之。
sysctl net.core.somaxconn
若其低于汝之php-fpm设定,则举之
# /etc/sysctl.d/99-php-fpm.conf
net.core.somaxconn = 8192
net.ipv4.tcp_max_syn_backlog = 8192
sudo sysctl -p /etc/sysctl.d/99-php-fpm.conf
此更易之后,当重启php-fpm,使监听者得接新之待办。重载不足,必复造此套接字。
sudo systemctl restart php8.4-fpm
谛听生产中队列之深浅以ss:
# Recv-Q on the LISTEN socket = current queue depth
ss -lntp | grep php-fpm
倘若有Recv-Q 之时,众工未克相随。积压之务,暂缓其时,非解通量之策。其解也,需增众工,速其码,或用Octane。
真实调优之程,于c5.large,Laravel 12
之设:c5.large,二CPU,四GB RAM,Laravel 12之应用,Telescope已启,MySQL别置一箱,Redis在本地,Nginx加php-fpm 8.4。
之先:
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 0
request_terminate_timeout = 0
listen.backlog = 511
以k6,200 RPS,5分钟载入测试。p99达4800毫秒。运行期间,稳态RAM占92%,OOM-killer触发两次。
后:
pm = static
pm.max_children = 20
pm.max_requests = 800
request_terminate_timeout = 35s
listen = /run/php/php8.4-fpm.sock
listen.backlog = 8192
# /etc/sysctl.d/99-php-fpm.conf
net.core.somaxconn = 8192
同负荷测试。九九分之百于三百八十毫秒。内存稳定在七八成。无内存溢出。吞吐量提升,盖因箱体已停分页。
其训:pm.max_children = 50于四吉字节之箱,非"扩展",实乃于交换区中积压请求数。减至二十则诸事皆速。
何时当转用Octane乎
辛烷(Swoole或FrankenPHP后端)使Laravel应用在请求间驻留于内存。自动加载不复行,服务容器不重建,中间件栈保持完整。那30-80毫秒的框架启动成本归零矣。
若汝之php-fpm工者,六十毫秒耗于框架之启动,四十毫秒用于实请求之劳,则Octane使汝于同此硬件上,得增通量二点五倍。此非调优之进,乃架构之变也。
Octane需汝之码慎之又慎。静态状态存于请求间,容器单例恒为单例跨请求,凡假定为新进程者,今皆谬误。高流量应用,此工实值为之。CMS若每秒十请求,php-fpm调适得宜,已足矣。
五设若此,可至八分之能,未改架构之先。Octane得余二分。先调适,后决断。
汝之今状何如?pm.max_children吾何故择此?请于注解中列汝之数字(RAM、平均工作者RSS、汝所定之值)。好奇众人之默认设置相去几何。
若此有益
php-fpm之调适,得箱体之宜,然箱体实乃应用规模渐缓之微末。其余之份(服务界限、框架耦合、决团队能否自调代码之层级),乃解耦PHP之所指。此乃架构层级,一旦逾越框架之常,代码库所求之阶也。
有Kindle、平装及精装版。英、德、日语版已出,葡、西语版将即至。













