1 bin HTTP isteğinin paralel tetiklenmesi takılıyor


10

Soru, 1k-2k giden HTTP isteklerini tetiklediğinizde gerçekte ne oluyor? Tüm bağlantıları 500 bağlantıyla kolayca çözeceğini görüyorum, ancak oradan yukarı doğru hareket etmek, bağlantılar açık bırakıldıkça ve Düğüm uygulaması orada sıkıştığından sorunlara neden görünüyor. Yerel sunucu + örnek Google ve diğer sahte sunucularla test edilmiştir.

Bu yüzden bazı farklı sunucu uç noktaları ile neden aldım: Sunucu isteği işleyemedi ve bir hata atmak iyi olan ECONNRESET okuyun. 1k-2k istek aralığında program sadece askıda kalır. Açık bağlantıları kontrol lsof -r 2 -i -aettiğinizde orada asılı kalan X miktarında bağlantı olduğunu görebilirsiniz 0t0 TCP 192.168.0.20:54831->lk-in-f100.1e100.net:https (ESTABLISHED). İsteklere zaman aşımı ayarı eklediğinizde, bunlar muhtemelen zaman aşımı hatasıyla sonuçlanır, ancak aksi halde bağlantı sonsuza kadar korunur ve ana program bazı limbo durumlarına neden olur?

Örnek kod:

import fetch from 'node-fetch';

(async () => {
  const promises = Array(1000).fill(1).map(async (_value, index) => {
    const url = 'https://google.com';
    const response = await fetch(url, {
      // timeout: 15e3,
      // headers: { Connection: 'keep-alive' }
    });
    if (response.statusText !== 'OK') {
      console.log('No ok received', index);
    }
    return response;
  })

  try {
    await Promise.all(promises);
  } catch (e) {
    console.error(e);
  }
  console.log('Done');
})();

1
Olabilir size sonrası sonuç npx envinfo, 8432.805ms benim Win 10 / nodev10.16.0 komut ucunda seni örnek çalıştıran
Łukasz Szewczak

Örneği OS X ve Alpine Linux'ta (docker container) çalıştırıyorum ve aynı sonuca ulaştım.
Risto Novik

Yerel mac'um komut dosyasını 7156.797ms'de çalıştırıyor. İstekleri engelleyen Güvenlik Duvarı olmadığından emin misiniz?
John

Yerel makine güvenlik duvarı kullanılmadan test edildi, ancak yerel yönlendiricim / ağımla ilgili bir sorun olabilir mi? Google Cloud veya Heroku'da benzer bir test yapmaya çalışacağım.
Risto Novik

Yanıtlar:


3

Kesin olanları anlamak için senaryonuzda bazı değişiklikler yapmam gerekiyordu, ama işte burada.

İlk olarak, nasıl nodeve nasıl event loopçalıştığını biliyor olabilirsiniz , ancak hızlı bir özet yapmama izin verin. Bir komut dosyasını çalıştırdığınızda, nodeçalışma zamanı önce eşzamanlı kısmını çalıştırır ve sonraki döngülerde çalıştırılacak promisesve zamanlanacak şekilde zamanlanır ve timersçözümlendiğinde, geri çağrıları başka bir döngüde çalıştırın. Bu basit özü, @StephenGrider'a çok iyi açıklıyor:


const pendingTimers = [];
const pendingOSTasks = [];
const pendingOperations = [];

// New timers, tasks, operations are recorded from myFile running
myFile.runContents();

function shouldContinue() {
  // Check one: Any pending setTimeout, setInterval, setImmediate?
  // Check two: Any pending OS tasks? (Like server listening to port)
  // Check three: Any pending long running operations? (Like fs module)
  return (
    pendingTimers.length || pendingOSTasks.length || pendingOperations.length
  );
}

// Entire body executes in one 'tick'
while (shouldContinue()) {
  // 1) Node looks at pendingTimers and sees if any functions
  // are ready to be called.  setTimeout, setInterval
  // 2) Node looks at pendingOSTasks and pendingOperations
  // and calls relevant callbacks
  // 3) Pause execution. Continue when...
  //  - a new pendingOSTask is done
  //  - a new pendingOperation is done
  //  - a timer is about to complete
  // 4) Look at pendingTimers. Call any setImmediate
  // 5) Handle any 'close' events
}

// exit back to terminal

Olay döngüsünün, bekleyen OS görevleri olana kadar hiçbir zaman sona ermeyeceğini unutmayın. Başka bir deyişle, bekleyen HTTP istekleri olana kadar düğüm yürütmeniz hiçbir zaman sona ermeyecektir.

Sizin durumunuzda, asyncher zaman bir söz döndüreceği için bir işlevi çalıştırır, bir sonraki döngü yinelemesinde yürütülmesini zamanlar. Eşzamansız işlevinizde, bu yinelemede bir defada başka 1000 vaat (HTTP isteği) zamanlayabilirsiniz map. Bundan sonra, programı bitirmek için hepsini çözmeyi bekliyorsunuz. Anonim ok işleviniz mapherhangi bir hata atmazsa, kesinlikle çalışacaktır . Sözünde biri bir hata atar ve bunu işlemek yoksa, sözlerin bazılarının geri arama programı yapma hiç denilen olmayacak sona ama değil çıkışında olay döngü o giderir kadar çıkmak için bunu engelleyecektir, çünkü tüm görevler, geri arama olmadan bile. Üzerinde dediği gibiPromise.all dokümanlar : ilk söz reddedilir reddedilmez.

Yani, ECONNRESEThata durumunda düğümün kendisi ile ilgili değildir, ağınızda bir hata atmak için getirilen ve daha sonra olay döngüsünün bitmesini önleyen bir şeydir. Bu küçük düzeltmeyle, tüm isteklerin eşzamansız olarak çözüldüğünü görebilirsiniz:

const fetch = require("node-fetch");

(async () => {
  try {
    const promises = Array(1000)
      .fill(1)
      .map(async (_value, index) => {
        try {
          const url = "https://google.com/";
          const response = await fetch(url);
          console.log(index, response.statusText);
          return response;
        } catch (e) {
          console.error(index, e.message);
        }
      });
    await Promise.all(promises);
  } catch (e) {
    console.error(e);
  } finally {
    console.log("Done");
  }
})();

Pedro, açıklama çabası için teşekkür ederim. İlk vaat reddi göründüğünde Promise.all'in reddedileceğini biliyorum, ancak çoğu durumda reddetmek için bir hata yoktu, bu yüzden her şey sadece boşta kalacaktı.
Risto Novik

1
> Bekleyen işletim sistemi görevleri olana kadar olay döngüsünün hiçbir zaman sona ermeyeceğini onarır. Başka bir deyişle, bekleyen HTTP istekleri olana kadar düğüm yürütmeniz hiçbir zaman sona ermeyecektir. Bu ilginç bir nokta gibi görünüyor, OS görevleri libuv üzerinden yönetiliyor mu?
Risto Novik

Sanırım libuv operasyonlarla ilgili daha fazla şey yapıyor (gerçekten çok iş parçacığı gerektiren şeyler). Ama yanılmış olabilirim, daha derinlemesine görmem gerek
Pedro Mutter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.