Як взагалі Node.js обробляє 10 000 одночасних запитів?


394

Я розумію, що Node.js використовує однопотоковий цикл і цикл подій для обробки запитів, які обробляють лише один за одним (що не блокує). Але все-таки, як це працює, давайте сказати 10 000 одночасних запитів. Цикл подій обробляє всі запити? Хіба це не зайняло б занадто багато часу?

Я не можу зрозуміти (поки), як це може бути швидше, ніж багатопотоковий веб-сервер. Я розумію, що багатопотоковий веб-сервер буде дорожчим за ресурси (пам'ять, процесор), але хіба все-таки не буде швидше? Я, мабуть, помиляюся; поясніть, будь ласка, як ця однопотокова швидкість у великій кількості запитів, і що вона зазвичай робить (на високому рівні) при обслуговуванні багатьох запитів, таких як 10 000.

А також, чи зможе ця однонитковий масштаб добре відповідати цій великій кількості? Будь ласка, майте на увазі, що я тільки починаю вивчати Node.js.


4
Оскільки більша частина роботи (переміщення даних) не передбачає процесор.
OrangeDog

4
Зауважте також, що те, що існує лише один потік Javascript, не означає, що багато інших потоків не працюють.
OrangeDog

Це питання або занадто широке, або повторник різних інших питань.
OrangeDog


Поряд з однократною нарізкою, Node.js робить щось, що називається "не блокуючи введення-виведення". Ось, де робиться вся магія
Ананд N

Відповіді:


762

Якщо вам доведеться задати це питання, ви, мабуть, незнайомі з тим, чим займаються більшість веб-додатків / послуг. Ви, напевно, думаєте, що все програмне забезпечення робить це:

user do an action
       
       v
 application start processing action
   └──> loop ...
          └──> busy processing
 end loop
   └──> send result to user

Однак це не так, як працюють веб-програми, або взагалі будь-які додатки з базою даних як бек-енд. Веб-додатки роблять це:

user do an action
       
       v
 application start processing action
   └──> make database request
          └──> do nothing until request completes
 request complete
   └──> send result to user

У цьому випадку програмне забезпечення витрачає більшу частину свого робочого часу, використовуючи 0% часу процесора в очікуванні повернення бази даних.

Додаток для багатопотокових мереж:

Багатопотокові мережеві програми обробляють вищенаведене навантаження таким чином:

request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request

Таким чином, потоки проводять більшу частину свого часу, використовуючи 0% процесора, чекаючи, коли база даних поверне дані. При цьому їм довелося виділити пам'ять, необхідну для потоку, що включає повністю окремий стек програми для кожного потоку тощо. дешево.

Однополосний цикл подій

Оскільки ми проводимо більшу частину часу, використовуючи 0% процесора, чому б не запустити якийсь код, коли ми не використовуємо процесор? Таким чином, кожен запит все одно отримає стільки ж часу процесора, скільки багатопотокові програми, але нам не потрібно запускати потік. Отже ми робимо це:

request ──> make database request
request ──> make database request
request ──> make database request
database request complete ──> send response
database request complete ──> send response
database request complete ──> send response

На практиці обидва підходи повертають дані приблизно з однаковою затримкою, оскільки саме час обробки реакції бази даних домінує в обробці.

Основна перевага тут полягає в тому, що нам не потрібно зароджувати нову нитку, тому нам не потрібно робити багато і багато молоток, який би сповільнив нас.

Чарівна, невидима нитка

На перший погляд загадкова річ - як обидва підходи, описані вище, встигають виконувати навантаження "паралельно"? Відповідь полягає в тому, що база даних є потоковою. Отже, наша однопоточна програма фактично використовує багатопотокове поведінку іншого процесу: бази даних.

Там, де односмуговий підхід не вдається

Додаток з однопотоковим зв’язком виходить з ладу, якщо вам потрібно зробити багато обчислень процесора перед поверненням даних. Тепер я не маю на увазі циклічну обробку результатів бази даних. Це все ще переважно O (n). Я маю на увазі такі речі, як перетворення Фур'є (кодування mp3, наприклад), трасування променів (3D-рендерінг) тощо.

Ще одна помилка однопотокових додатків полягає в тому, що він буде використовувати лише одне ядро ​​CPU. Отже, якщо у вас є чотириядерний сервер (не рідкість сьогодні), ви не використовуєте інші 3 ядра.

Там, де багатопотоковий підхід не вдається

Багатопотокове додаток не вдається отримати велику кількість, якщо вам потрібно виділити багато оперативної пам’яті на потік. По-перше, саме використання оперативної пам'яті означає, що ви не можете обробити стільки запитів, як однопотокове додаток. Гірше, малок повільний. Виділення великої кількості об'єктів (що є звичайним для сучасних веб-рамок) означає, що ми можемо бути більш повільними, ніж однопоточні програми. Тут зазвичай виграють node.js.

Один із випадків використання, які в результаті покращують багатопотоковість, - це коли потрібно запустити іншу мову сценаріїв у своїй темі. Спочатку вам зазвичай потрібно провести весь час виконання цієї мови, а потім потрібно змістити змінні, використовувані вашим сценарієм.

Тож якщо ви пишете мережеві додатки на C або go або java, то накладні нарізки зазвичай не будуть занадто поганими. Якщо ви пишете веб-сервер C для обслуговування PHP або Ruby, тоді написати швидший сервер дуже просто в JavaScript або Ruby або Python.

Гібридний підхід

Деякі веб-сервери використовують гібридний підхід. Наприклад, Nginx і Apache2 реалізують свій мережевий код обробки як пул потоків циклів подій. Кожен потік виконує цикл подій, одночасно обробляючи запити однопотокових, але запити врівноважуються між кількома потоками.

Деякі архітектури з однопотоковими схемами також використовують гібридний підхід. Замість запуску декількох потоків з одного процесу ви можете запустити кілька додатків - наприклад, 4 сервери node.js на чотирьохядерній машині. Потім ви використовуєте балансир навантаження, щоб розподілити навантаження між процесами.

По суті, два підходи є технічно однаковими дзеркальними зображеннями один одного.


104
Це, безумовно, найкраще пояснення для вузла, який я прочитав до цих пір. Це "однопотокове додаток насправді використовує багатопотокове поведінку іншого процесу: бази даних." Зробив свою роботу
kenobiwan

як щодо того, якщо клієнт робить кілька запитів у вузлі, наприклад, наприклад, отримання імені та модифікації його, і скажімо, що ці операції підштовхують до сервера, щоб дуже багато клієнтів обробляти. як я міг впоратися з таким сценарієм?
Ремаріо

3
@CaspainCaldion Це залежить від того, що ти маєш на увазі під дуже швидкими та великими клієнтами. Так, node.js може обробляти до 1000 запитів в секунду і швидкість, обмежена лише швидкістю вашої мережевої карти. Зауважте, що це 1000 запитів в секунду, а не клієнтів, підключених одночасно. Він може обробляти 10000 одночасних клієнтів без проблем. Справжнє вузьке місце - мережева карта.
slebetman

1
@slebetman, найкраще пояснення будь-коли. одна річ, якщо у мене є алгоритм машинного навчання, який обробляє деяку інформацію та дає результати відповідно, чи повинен я використовувати
мультипотоковий

5
@GaneshKarewad Алгоритми використовують процесор, сервіси (база даних, REST API тощо) використовують введення / виведення. Якщо AI - це алгоритм, записаний у js, то слід запустити його в інший потік або процес. Якщо AI - це послуга, що працює на іншому комп’ютері (наприклад, Amazon або Google або IBM AI), то використовуйте єдину потокову архітектуру.
slebetman

46

Як ви думаєте, ви думаєте, що більша частина обробки обробляється в циклі подій вузла. Вузол фактично відключає роботу вводу / виводу для потоків. Операції вводу / виводу, як правило, приймають замовлення на величину довше, ніж операції з процесором, тому чому ЦП чекати цього? Крім того, ОС вже дуже добре справляється із завданнями вводу / виводу. Насправді, оскільки Node не чекає навколо, він досягає значно більшого використання процесора.

Аналогічно, подумайте про NodeJS як про офіціанта, який приймає замовлення клієнтів, а шеф-кухарі готують їх на кухні. В інших системах є кілька шеф-кухарів, які приймають замовлення клієнтів, готують страву, прибирають стіл і лише потім відвідують наступного клієнта.


5
Дякуємо за аналогію ресторану! Я знаходжу аналогії та реальні приклади, на яких набагато простіше вчитися.
LaVache

13

Я розумію, що Node.js використовує однопотоковий цикл і цикл подій для обробки запитів, які обробляють лише один за одним (що не блокує).

Я можу нерозуміти те, що ви тут сказали, але "один за одним" звучить так, що ви, можливо, не повністю розумієте архітектуру, що базується на подіях.

У «звичайній» (не керованій подіями) архітектурі додатків процес проводить багато часу, сидячи навколо, чекаючи, що щось станеться. У архітектурі, що базується на подіях, як-от Node.js, процес не просто чекає, він може продовжуватися з іншою роботою.

Наприклад: ви отримуєте з'єднання від клієнта, ви приймаєте його, ви читаєте заголовки запиту (у разі http), потім починаєте діяти на запит. Ви можете прочитати орган запиту, і, як правило, ви надсилаєте клієнту деякі дані назад (це навмисне спрощення процедури, лише щоб продемонструвати точку).

На кожному з цих етапів більшість часу витрачається на очікування надходження одних даних з іншого кінця - фактичний час, витрачений на обробку в основній потоці JS, зазвичай досить мінімальний.

Коли стан об'єкта вводу-виводу (наприклад, підключення до мережі) змінюється таким чином, що йому потрібна обробка (наприклад, дані надходять у сокет, сокет стає доступним для запису тощо), головний потік JS Node.js прокидається зі списком елементів, які потребують обробки.

Він знаходить відповідну структуру даних і видає якусь подію цієї структури, яка викликає запуск зворотних викликів, які обробляють вхідні дані, або записують більше даних у сокет тощо. Після того, як усі об'єкти вводу-виводу, які потребують обробки, були обробляється, головний потік JS Node.js знову зачекає, поки не скаже, що доступно більше даних (або якась інша операція завершена або закінчена).

Наступного разу, коли він буде пробуджений, це може бути пов'язано з іншим об'єктом вводу-виводу, який потрібно обробити - наприклад, іншим мережевим підключенням. Кожен раз, коли запускаються відповідні зворотні дзвінки, після чого він повертається до сну, очікуючи, що трапиться щось інше.

Важливим моментом є те, що обробка різних запитів перемежована, вона не обробляє один запит від початку до кінця, а потім переходить до наступного.

На мій погляд, головна перевага цього полягає в тому, що повільний запит (наприклад, ви намагаєтеся надіслати 1 МБ даних відповідей на мобільний телефон через з'єднання даних 2G або ви робите дуже повільний запит до бази даних) " t блокувати швидші.

У звичайному багатопотоковому веб-сервері у вас зазвичай буде потік для кожного запиту, який обробляється, і він буде обробляти ТІЛЬКИ цей запит до його завершення. Що станеться, якщо у вас багато повільних запитів? Зрештою, безліч ваших потоків висить навколо обробки цих запитів, а інші запити (які можуть бути дуже простими запитами, які можна обробити дуже швидко) отримують чергу за ними.

Окрім Node.js, існує безліч інших систем на основі подій, і вони, як правило, мають подібні переваги та недоліки порівняно зі звичайною моделлю.

Я б не стверджував, що системи, що базуються на подіях, швидші у будь-якій ситуації або при кожному навантаженні - вони, як правило, працюють добре для навантажень, пов'язаних з входом / виводом, не так добре для процесорів, пов'язаних з процесором.


12

Етапи опрацювання моделі циклу одиночної нитки подій:

  • Клієнти Надіслати запит на веб-сервер.

  • Веб-сервер Node JS внутрішньо підтримує пул обмежених ниток для надання послуг клієнтським запитам.

  • Веб-сервер Node JS отримує ці запити та розміщує їх у черзі. Він відомий як "Черга подій".

  • Внутрішній веб-сервер вузла JS має компонент, відомий як "цикл подій". Чому він отримав цю назву, це те, що він використовує невизначений цикл для отримання запитів та їх обробки.

  • У циклі подій використовується лише одна нитка. Це головне серце моделі обробки платформи NS JS.

  • Петля подій перевіряє, чи будь-який запит клієнта розміщений у черзі подій. Якщо ні, то чекайте вхідних запитів нескінченно.

  • Якщо так, то підберіть один запит клієнта з черги подій

    1. Починає процес, що вимагає клієнт
    2. Якщо цей запит клієнта не вимагає блокування операцій вводу-виводу, тоді обробіть все, підготуйте відповідь і відправте його назад клієнту.
    3. Якщо цей запит клієнта потребує деяких операцій блокування вводу-виводу, таких як взаємодія з базою даних, файловою системою, зовнішніми службами, тоді він буде дотримуватися іншого підходу
  • Перевіряє наявність ниток із внутрішнього пулу ниток
  • Візьміть одну нитку та призначте цей запит клієнта цій темі.
  • Ця нитка відповідає за прийняття цього запиту, обробку його, виконання операцій блокування вводу-виводу, підготовку відповіді та відправлення назад у цикл подій

    дуже добре пояснюється @Rambabu Пози для більш докладного пояснення йде кинути цю посилання


Діаграма, наведена у цій публікації щоденника, здається, не є правильною, те, що вони згадували у цій статті, не є абсолютно правильним.
rranj

11

Додавання відповіді до slebetman: Коли ви говорите, що Node.JSможе обробити 10000 одночасних запитів, вони по суті не блокують запити, тобто ці запити в основному стосуються запиту бази даних.

Внутрішньо, event loopвикористовується Node.JSобробка a thread pool, де кожен потік обробляє a non-blocking requestі цикл подій продовжує слухати більше запиту після делегування роботи в один з потоків thread pool. Коли одна з ниток завершує роботу, вона надсилає сигнал тому, event loopщо вона закінчила ака callback. Event loopпотім обробіть цей зворотний дзвінок і відправте відповідь назад.

З новинкою у NodeJS читайте докладніше, nextTickщоб зрозуміти, як працює цикл подій всередині. Читайте блоги на http://javascriptissexy.com , вони були мені дуже корисні, коли я почав працювати з JavaScript / NodeJS.


2

Додавання до slebetman для більшої чіткості щодо того, що відбувається під час виконання коду.

Внутрішній пул потоків у nodeJs за замовчуванням містить 4 потоки. і не так, як весь запит приєднаний до нової нитки з пулу потоків, все виконання запиту відбувається так само, як і будь-який звичайний запит (без будь-якого завдання блокування), тільки що коли запит має тривалу роботу або важку операцію, наприклад, db виклик, операція з файлом або запит http, завдання ставиться в чергу до внутрішнього пулу потоків, який надається libuv. Оскільки nodeJs надає 4 потоки у внутрішньому пулі потоків за замовчуванням, кожен 5-й або наступний паралельний запит чекає, поки потік буде вільним і як тільки ці операції закінчуються, зворотний виклик буде висунутий у чергу зворотного виклику. і вибирається циклом подій і відсилає відповідь.

Тепер тут надходить ще одна інформація про те, що в її не один раз черговий зворотний дзвінок, існує безліч черг.

  1. NextTick черга
  2. Черга мікрозадач
  3. Черга таймерів
  4. Черга зворотного виклику IO (Запити, Опції файлів, ДБО)
  5. Черга опитування IO
  6. Перевірте «Фаза черги» або «SetImmediate»
  7. закрити чергу обробників

Кожен раз, коли надходить запит, код отримує виконання в цьому порядку зворотних викликів.

Це не так, як коли є запит на блокування, він додається до нової нитки. За замовчуванням є лише 4 потоки. Отже, там відбувається чергова черга.

Кожного разу, коли в коді відбувається процес блокування, подібний до зчитування файлу, тоді викликає функцію, яка використовує потік з пулу потоків, а потім, коли операція виконана, зворотний виклик передається у відповідну чергу і потім виконується в порядку.

Все виходить у чергу залежно від типу зворотного виклику та обробляється у порядку, зазначеному вище.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.