PHP-FPM pool ayarında 300 istekte tıkanan kuyruğu yeniden ölçmek

这篇内容暂时只有 Türkçe 版本,以下是原文。

Ü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
赞同度 100% · 0 条评论 · 4 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

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