1k HTTP 요청의 병렬 트리거가 중단됩니다


10

문제는 1k-2k 나가는 HTTP 요청을 트리거 할 때 실제로 어떻게됩니까? 500 개의 연결로 모든 연결을 쉽게 해결할 수 있지만 연결을 열어두고 노드 앱이 멈 추면 문제가 발생하는 것으로 보입니다. 로컬 서버 + 예제 Google 및 기타 모의 서버로 테스트했습니다.

그래서 몇 가지 다른 서버 끝점으로 이유를 얻었습니다 .ECONNRESET을 읽으십시오.이 서버는 요청을 처리하고 오류를 처리 할 수 ​​없었습니다. 1k-2k 요청 범위에서는 프로그램이 중단됩니다. 열린 연결을 확인하면 lsof -r 2 -i -aX 연결이 계속 걸려 있음을 알 수 있습니다 0t0 TCP 192.168.0.20:54831->lk-in-f100.1e100.net:https (ESTABLISHED). 요청에 시간 초과 설정을 추가하면 시간 초과 오류가 발생하지만 연결이 영원히 유지되고 주 프로그램이 일부 림보 상태로 끝나는 이유는 무엇입니까?

예제 코드 :

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
의 결과를 게시 npx envinfo하여 내 Win 10 / nodev10.16.0 스크립트에서 예제를 실행 하면 8432.805ms로 끝날 수 있습니다
Łukasz Szewczak

OS X와 ​​Alpine Linux (docker container)에서 예제를 실행하고 동일한 결과에 도달했습니다.
Risto Novik

내 로컬 맥은 7156.797ms에 스크립트를 실행합니다. 요청을 차단하는 방화벽이 없습니까?
요한

로컬 컴퓨터 방화벽을 사용하지 않고 테스트했지만 로컬 라우터 / 네트워크에 문제가있을 수 있습니까? Google Cloud 또는 Heroku에서 유사한 테스트를 실행하려고합니다.
Risto Novik

답변:


3

확실히 무슨 일이 일어나고 있는지 이해하기 위해, 나는 당신의 스크립트를 약간 수정해야했지만 여기에 있습니다.

먼저, 방법 nodeevent loop작동 방식을 알 수 있지만 간단히 살펴 보겠습니다. 스크립트를 실행하면 node런타임 처음으로 다음 일정을 그것의 동기 부분을 실행 promises하고 timers다음 루프에서 실행하고, 그들이 해결을 선택하면, 또 다른 루프에서 콜백을 실행합니다. 이 간단한 요점은 @StephenGrider의 신용을 잘 설명합니다.


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

보류중인 OS 작업이있을 때까지 이벤트 루프가 종료되지 않습니다. 즉, 보류중인 HTTP 요청이있을 때까지 노드 실행이 끝나지 않습니다.

귀하의 경우에는 async항상 약속을 반환하므로 다음 루프 반복에서 실행되도록 예약합니다. 비동기 기능에서는 반복 시 한 번에 1000 개의 다른 약속 (HTTP 요청)을 한 번에 예약합니다 map. 그 후, 당신은 프로그램을 완료하기 위해 모든 해결을 기다리고 있습니다. 익명 화살표 기능이 오류를 발생map 시키지 않으면 확실히 작동 합니다 . 약속 중 하나에 오류가 발생하고 처리하지 않으면 약속 중 일부는 콜백을 호출하여 프로그램을 종료 하지만 종료 하지 않습니다 . 이벤트 루프는 해결 될 때까지 종료되지 않기 때문에 콜백 없이도 모든 작업. 그것이 말했듯이Promise.all docs : 첫 번째 약속이 거부되는 즉시 거부합니다.

따라서 ECONNRESET오류가 발생하면 노드 자체와 관련이 없으며 네트워크에서 가져와 오류를 발생시킨 다음 이벤트 루프가 종료되는 것을 방지합니다. 이 작은 수정을 사용하면 모든 요청이 비동기 적으로 해결되는 것을 볼 수 있습니다.

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");
  }
})();

페드로는 설명해 주셔서 감사합니다. Promise.all은 첫 번째 약속 거부가 표시되면 거부하지만 대부분의 경우 거부 오류가 없으므로 모든 것이 유휴 상태가됩니다.
Risto Novik

1
> 보류중인 OS 작업이있을 때까지 이벤트 루프가 끝나지 않도록 복구합니다. 다시 말해, 보류중인 HTTP 요청이있을 때까지 노드 실행이 끝나지 않습니다. 흥미로운 점은 OS 작업이 libuv를 통해 관리됩니까?
Risto Novik

libuv는 작업과 관련된 더 많은 것을 처리한다고 생각합니다 (실제로 멀티 스레딩이 필요한 것). 그러나 나는 틀렸을 수 있고, 더 깊이 볼 필요가있다
Pedro Mutter
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.