문제는 1k-2k 나가는 HTTP 요청을 트리거 할 때 실제로 어떻게됩니까? 500 개의 연결로 모든 연결을 쉽게 해결할 수 있지만 연결을 열어두고 노드 앱이 멈 추면 문제가 발생하는 것으로 보입니다. 로컬 서버 + 예제 Google 및 기타 모의 서버로 테스트했습니다.
그래서 몇 가지 다른 서버 끝점으로 이유를 얻었습니다 .ECONNRESET을 읽으십시오.이 서버는 요청을 처리하고 오류를 처리 할 수 없었습니다. 1k-2k 요청 범위에서는 프로그램이 중단됩니다. 열린 연결을 확인하면 lsof -r 2 -i -a
X 연결이 계속 걸려 있음을 알 수 있습니다 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');
})();
답변
확실히 무슨 일이 일어나고 있는지 이해하기 위해, 나는 당신의 스크립트를 약간 수정해야했지만 여기에 있습니다.
먼저, 방법 node
과 event 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");
}
})();