Üretimde PHP 8.2, php-fpm 8.2, nginx 1.24, 4 çekirdek / 8 GB bir kutu; günde ~2.1 milyon istek. Saat 14:00 civarı p95 240 ms’den 1.8 s’ye çıkıyor, hata oranı %0.3. Nginx error log’da “upstream timed out” değil, “no live upstreams” görünüyor. Kutuda bellek %60, CPU %35. Yani kaynak yok değil; havuz tıkanıyor.
Önce varsayımı ölçtüm. pm = dynamic, pm.max_children = 40, pm.start_servers = 10, pm.min_spare_servers = 8, pm.max_spare_servers = 20. İsteklerin %70’i 40 ms, %5’i 2-4 s süren bir dış çağrı (ödeme sağlayıcısı) içeriyor. 40 çocuğun 40’ı da o yavaş çağrıda beklediğinde kuyruk patlıyor.
Ölçüm için pool’a status sayfası açtım ve 5 saniyede bir çektim:
location = /fpm-status {
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
; www.conf
pm.status_path = /fpm-status
ping.path = /fpm-ping
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 1s
request_terminate_timeout = 15s
Sonra bir dakikalık örnek:
for i in $(seq 1 12); do
curl -s http://127.0.0.1/fpm-status?full | grep -E 'active processes|listen queue|max listen queue'
sleep 5
done
listen queue 0’dan 60’a çıkıyor, max listen queue 71. max_children 40 sabit. Yani sıra sokette birikiyor. Ayrıca slowlog üst üste aynı yığını gösterdi: curl_exec → PaymentClient::charge. Tahmin değil, kanıt.
Çözüm için iki iş yaptım. Birincisi, sağlayıcı çağrısına kendi zaman aşımını koydum (bağlantı 1 s, toplam 3 s), ikincisi havuzu ikiye böldüm:
; www.conf — genel trafik
pm = dynamic
pm.max_children = 48
pm.start_servers = 12
pm.min_spare_servers = 10
pm.max_spare_servers = 24
pm.max_requests = 800
; odeme.conf — sadece ödeme rotaları
pm = ondemand
pm.max_children = 12
pm.process_idle_timeout = 20s
nginx tarafında /odeme/ prefix’ini ikinci upstream’e yönlendirdim. Genel havuz artık yavaş çağrı yüzünden dolmuyor; ödeme havuzu dolsa bile katalog sayfaları etkilenmiyor.
Neden max_children’ı 200 yapmadım? Çünkü her çocuk ~35-60 MB RSS tutuyor; 200 çocuk kutuyu swap’e sokar, gecikmeyi düşürmez artırır. Asıl darboğaz işlem sayısı değil, yavaş dış çağrının işlem kiralıyor olması. Havuzu bölmek, kiralanan işlemi izole ediyor. Bedeli: iki havuzun boşta tuttuğu süreçler yüzünden ~150 MB ek bellek ve iki config’i ayrı izleme zorunluluğu.
Nerede işe yaramaz: Trafiğin çoğu yavaş uç noktadan geliyorsa havuzu bölmek sadece acıyı ikiye böler. Ayrıca pm = ondemand altında ani patlamalarda süreç açma maliyeti görünür; 20 s’lik process_idle_timeout bizde iyi çalıştı, ama çok kısa tutarsanız sürekli fork/collapse döngüsü olur. Bir de pm.max_requests ile süreç geri dönüşümü, uzun süreli bağlantı havuzu kullanan kodda bağlantıları koparır; bizde her istek bağlantıyı kendi açtığı için sorun olmadı.
Sizin kurulumda listen queue ile max_children oranı ne durumda? Özellikle dış API çağrısı yapan rotaları ayrı havuzda mı tutuyorsunuz, yoksa tek havuzda zaman aşımıyla mı sınırlıyorsunuz?


0 条评论
还没有评论,写下第一条。