PHP 8.2'de file_get_contents ile uzak API'den 12 bin kayıt çekerken soket zaman aşımı
这篇内容暂时只有 Türkçe 版本,以下是原文。
Paylaşımlı bir sunucuda (PHP 8.2.13, Ubuntu 22.04, php-fpm 8.2) çalışan bir iç panelimiz var. Her sabah 04:00’te bir cron, tedarikçinin REST API’sinden sayfa sayfa 12 bin ürün kaydı çekiyor, her sayfa 500 kayıt. Toplam veri ~9 MB JSON. Sorun şuydu: cron bazen 3 dakikada bitiyordu, bazen 27. dakikada soket zaman aşımıyla ölüyordu. Aynı kod, aynı veri, farklı gün farklı sonuç.
Önce kodun ilgili kısmına baktım; basitleştirilmiş hali şöyleydi:
$ctx = stream_context_create([
'http' => [
'timeout' => 30,
'ignore_errors' => true,
],
]);
for ($page = 1; $page <= 24; $page++) {
$url = "https://api.tedarikci.example/urunler?page={$page}&limit=500";
$json = file_get_contents($url, false, $ctx);
$rows = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
foreach ($rows['data'] as $row) {
// upsert
}
}
timeout 30 saniye görünüyor ama bu değer bağlantı ve okuma için toplam süre değil; her soket işlemi için ayrı ayrı uygulanıyor. Ayrıca DNS çözümlemesi ve TLS el sıkışması bu sayaca dahil edilmiyor gibi davranıyor. 500 kayıtlık bir sayfa normalde 1-2 saniyede geliyor, ama tedarikçi tarafında yavaş bir replika devreye girdiğinde ilk bayt 40 saniye sonra geliyor ve file_get_contents tam o anda pes ediyor.
Yaptığım şey curl’e geçmek oldu, ama sadece geçmek yetmedi; iki ayrı zaman aşımı tanımladım:
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5, // bağlantı kurulumu
CURLOPT_TIMEOUT => 90, // toplam işlem
CURLOPT_LOW_SPEED_LIMIT => 1024,
CURLOPT_LOW_SPEED_TIME => 20, // 20 sn boyunca <1 KB/s ise kes
CURLOPT_HTTPHEADER => ['Accept: application/json'],
]);
$json = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($json === false || $code >= 500) {
throw new RuntimeException('sayfa alinamadi: ' . curl_error($ch));
}
LOW_SPEED_LIMIT/LOW_SPEED_TIME kombinasyonu asıl farkı yarattı: bağlantı açık kalıp veri damla damla gelirse 90 saniye beklemek yerine 20 saniyede kesiyoruz. 24 sayfa için de bir yeniden deneme döngüsü ekledim, ama toplam süreye 5 dakika sınırı koydum; sınır aşılırsa o günün işi yarıda kalıyor ve ertesi gün tam çekim yapılıyor. Yarım veriyle devam etmek, ilk günlerde olduğu gibi sessizce eksik ürün bırakmaktan iyi.
Bunun bir bedeli var: curl uzantısına bağımlı hale geldim; paylaşımlı sunucularda bazen kapalı oluyor. Ayrıca CURLOPT_TIMEOUT tek başına yavaş ama sürekli akan bir yanıtı kesmez, bu yüzden düşük hız eşiğini de doğru ayarlamak gerekiyor. Çok küçük eşik (ör. 10 KB/s) normal yavaş sayfaları da öldürüyor.
Nerede işe yaramaz: tedarikçi sayfa başına 500 yerine 5 bin kayıt döndürmeye başlarsa tek yanıt 40 MB olur ve bellek tarafı ayrı bir sorun olur; bu ayar onu çözmez. Ayrıca API anahtarı query string’deyse ve yönlendirme varsa, curl varsayılan olarak Authorization başlığını farklı host’a taşımaz; bunu CURLOPT_FOLLOWLOCATION ile birlikte test etmek gerekiyor. Bir de cron’u flock ile kilitlemeyi unutmuştum; iki kopya aynı anda çalışınca upsert’ler çakıştı, onu ayrıca ekledim.
Sizin üretimde uzak API çekerken zaman aşımını bağlantı ve okuma olarak ayırdığınız bir yer var mı, yoksa tek bir toplam süreyle mi idare ediyorsunuz?


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