Порядок дочерних событий в Node.js
У меня есть API, его рабочий процесс выглядит так:
делая некоторую логику, используя процессорное время 1 секунды
дождитесь ввода-вывода в сети, и для этого ввода-вывода тоже потребуется 1 секунда.
Таким образом, обычно для этого API требуется около 2 секунд, чтобы ответить
Затем я сделал тест. Я запускаю 10 запросов одновременно. КАЖДОМУ из них нужно более 10 секунд, чтобы ответить
Этот тест означает, что Node завершит всю дорогостоящую часть процессора для всех 10 запросов.
ЗАЧЕМ? почему он не отвечает на один запрос сразу после того, как один IO сделан.
Спасибо за комментарии. Я думаю, что мне нужно сделать некоторые объяснения по поводу моей проблемы.
Что меня беспокоит, так это если количество запросов не равно 10, если есть 100 запросов одновременно. Все они будут тайм-аут!
Если Узел немедленно отреагирует на событие IO ребенка, я думаю, что по крайней мере 20% из них не выйдут из строя.
Я думаю, что узлу нужен какой-нибудь механизм приоритета событий
router.use('/test/:id', function (req, res) {
var id = req.param('id');
console.log('start cpu code for ' + id);
for (var x = 0; x < 10000; x++) {
for (var x2 = 0; x2 < 30000; x2++) {
x2 -= 1;
x2 += 1;
}
}
console.log('cpu code over for ' + id);
request('http://terranotifier.duapp.com/wait3sec/' + id, function (a,b,data) {
// how can I make this code run immediately after the server response to me.
console.log('IO over for ' + data);
res.send('over');
});
});
1 ответ
Node.js является однопоточным. Поэтому, пока у вас есть долго работающая подпрограмма, она не может обрабатывать другие части кода. Обидный фрагмент кода в этом случае - ваш двойной цикл for, который занимает много процессорного времени.
Чтобы сначала понять, что вы видите, позвольте мне объяснить, как работает цикл обработки событий.
Цикл событий Node.js развился из цикла событий javascript, который развился из цикла событий веб-браузеров. Цикл событий веб-браузера изначально был реализован не для javascript, а для обеспечения прогрессивной визуализации изображений. Цикл событий выглядит примерно так:
,-> is there anything from the network?
| | |
| no yes
| | |
| | '-----------> read network data
| V |
| does the DOM need updating? <-------------'
| | |
| no yes
| | |
| | v
| | update the DOM
| | |
'------'--------------'
Когда был добавлен javascript, обработка сценария была просто вставлена в цикл обработки событий:
,-> is there anything from the network?
| | |
| no yes
| | |
| | '-----------> read network data
| V |
| any javascript to run? <------------------'
| | |
| no yes
| | '-----------> run javascript
| V |
| does the DOM need updating? <-------------'
| | |
| no yes
| | |
| | v
| | update the DOM
| | |
'------'--------------'
Когда механизм javascript запускается за пределами браузера, как в Node.js, связанные с DOM части просто удаляются, и ввод / вывод становится обобщенным:
,-> any javascript to run?
| | |
| no yes
| | |
| | '--------> RUN JAVASCRIPT
| V |
| is there any I/O <------------'
| | |
| no yes
| | |
| | v
| | read I/O
| | |
'------'--------------'
Обратите внимание, что весь ваш код JavaScript выполняется в части RUN JAVASCRIPT.
Итак, что происходит с вашим кодом, когда вы делаете 10 подключений?
connection1: node accepts your request, processes the double for loops
connection2: node is still processing the for loops, the request gets queued
connection3: node is still processing the for loops, the request gets queued
(at some point the for loop for connection 1 finishes)
node notices that connection2 is queued so connection2 gets accepted,
process the double for loops
...
connection10: node is still processing the for loops, the request gets queued
(at this point node is still busy processing some other for loop,
probably for connection 7 or something)
request1: node is still processing the for loops, the request gets queued
request2: node is still processing the for loops, the request gets queued
(at some point all connections for loops finishes)
node notices that response from request1 is queued so request1 gets processed,
console.log gets printed and res.send('over') gets executed.
...
request10: node is busy processing some other request, request10 gets queued
(at some point request10 gets executed)
Вот почему вы видите, что узел занимает 10 секунд, отвечая на 10 запросов. Дело не в том, что сами запросы медленные, но их ответы ставятся в очередь за всеми циклами for, и циклы for выполняются первыми (потому что мы все еще находимся в текущем цикле цикла обработки событий).
Чтобы противостоять этому, вы должны сделать циклы for асинхронными, чтобы дать узлу возможность обработать цикл событий. Вы можете написать их на C и использовать C для запуска независимых потоков для каждого из них. Или вы можете использовать один из потоковых модулей из npm для запуска javascript в отдельных потоках. Или вы можете использовать worker-потоки, которые являются веб-работниками, такими как API, реализованный для Node.js. Или вы можете разветвить кластер процессов для их выполнения. Или вы можете просто зациклить их с помощью setTimeout, если параллелизм не критичен:
router.use('/test/:id', function (req, res) {
var id = req.param('id');
console.log('start cpu code for ' + id);
function async_loop (count, callback, done_callback) {
if (count) {
callback();
setTimeout(function(){async_loop(count-1, callback)},1);
}
else if (done_callback) {
done_callback();
}
}
var outer_loop_done=0;
var x2=0;
async_loop(10000,function(){
x1++;
async_loop(30000,function(){
x2++;
},function() {
if (outer_loop_done) {
console.log('cpu code over for ' + id);
request('http://terranotifier.duapp.com/wait3sec/' + id,
function (a,b,data){
console.log('IO over for ' + data);
res.send('over');
}
);
}
});
},function(){
outer_loop_done = 1;
});
});
Приведенный выше код будет обрабатывать ответ от request()
как можно скорее, а не ждать всех async_loop
s, чтобы выполнить до завершения, не используя потоки (таким образом никакой параллелизм), но просто используя приоритет очереди событий.