Как да мащабирате вашия сървър Node.js с помощта на клъстериране

Мащабируемостта е актуална тема в технологиите и всеки език за програмиране или рамка предоставя собствен начин за справяне с големи натоварвания от трафик.

Днес ще видим лесен и ясен пример за клъстериране на Node.js. Това е техника за програмиране, която ще ви помогне да паралелизирате кода си и да ускорите производителността.

„Един екземпляр на Node.js работи в една нишка. За да се възползва от многоядрените системи, потребителят понякога ще иска да стартира клъстер от Node.js процеси, за да се справи с товара. "

- Документация на Node.js

Ще създадем прост уеб сървър, използващ Koa, който наистина е подобен на Express по отношение на използването.

Пълният пример е достъпен в това хранилище на Github.

Какво ще изградим

Ще изградим прост уеб сървър, който ще действа по следния начин:

  1. Нашият сървър ще получи POSTзаявка, ще се преструваме, че потребителят ни изпраща снимка.
  2. Ще копираме изображение от файловата система във временна директория.
  3. Ще го обърнем вертикално, използвайки Jimp, библиотека за обработка на изображения за Node.js.
  4. Ще го запазим във файловата система.
  5. Ще го изтрием и ще изпратим отговор на потребителя.

Разбира се, това не е приложение от реалния свят, но е доста близо до едно. Ние просто искаме да измерим ползите от използването на клъстериране.

Създаване на проекта

Ще използвам, за yarnда инсталирам зависимостите си и да инициализирам проекта си:

Тъй като Node.js е с една нишка, ако уеб сървърът ни се срине, той ще остане неработещ, докато някой друг процес не го рестартира. Така че ще инсталираме завинаги, прост демон, който ще рестартира нашия уеб сървър, ако някога се срине.

Също така ще инсталираме Jimp, Koa и Koa Router.

Първи стъпки с Koa

Това е структурата на папките, която трябва да създадем:

Ще имаме srcпапка, която съдържа два JavaScript файла: cluster.jsи standard.js.

Първият ще бъде файлът, в който ще експериментираме с clusterмодула. Вторият е прост Koa сървър, който ще работи без никакво клъстериране.

В moduleдиректорията ще създадем два файла: job.jsи log.js.

job.jsще извърши работата по обработка на изображения log.jsще регистрира всяко събитие, което се случва по време на този процес.

Модулът Log

Log модулът ще бъде проста функция, която ще вземе аргумент и ще го запише в stdout(подобно на console.log).

Той също така ще добави текущия клеймо в началото на дневника. Това ще ни позволи да проверим кога процесът е стартирал и да измерим неговата ефективност.

Модулът за работа

Ще бъда честен, това не е красив и супер оптимизиран сценарий. Това е просто лесна работа, която ще ни позволи да стресираме нашата машина.

Уеб сървърът на Koa

Ще създадем много прост уеб сървър. Той ще отговори на два маршрута с два различни HTTP метода.

Ще можем да изпълним GET заявка на //localhost:3000/. Koa ще отговори с прост текст, който ще ни покаже текущия PID (идентификатор на процеса).

Вторият маршрут ще приема само POST заявки по /flipпътя и ще изпълнява задачата, която току-що създадохме.

Също така ще създадем прост междинен софтуер, който ще зададе X-Response-Timeхедър. Това ще ни позволи да измерим ефективността.

Страхотен! Вече можем да започнем да въвеждаме сървъри node ./src/standard.jsи да тестваме маршрутите си.

Проблемът

Нека използваме моята машина като сървър:

  • Macbook Pro 15-инчов 2016
  • 2,7 GHz Intel Core i7
  • 16GB RAM

Ако направя POST заявка, скриптът по-горе ще ми изпрати отговор за ~ 3800 милисекунди. Не е толкова лошо, като се има предвид, че изображението, върху което работя в момента, е около 6.7MB.

Мога да опитам да направя още заявки, но времето за отговор няма да намалее твърде много. Това е така, защото заявките ще се изпълняват последователно.

И така, какво би се случило, ако се опитам да направя 10, 100, 1000 едновременни заявки?

Направих прост скрипт Elixir, който изпълнява множество едновременни HTTP заявки:

Избрах Elixir, защото наистина е лесно да създавате паралелни процеси, но можете да използвате каквото предпочитате!

Тестване на десет едновременни заявки - без клъстериране

Както можете да видите, ние произвеждаме 10 едновременни процеса от нашия iex (Elixir REPL).

Сървърът Node.js веднага ще копира нашето изображение и ще започне да го обръща.

Първият отговор ще бъде регистриран след 16 секунди, а последният след 40 секунди.

Такова драматично намаляване на производителността! Само с 10 едновременни заявки,намалихме производителността на уеб сървъра с 950%!

Представяме клъстериране

Помните ли какво споменах в началото на статията?

За да се възползва от многоядрените системи, потребителят понякога ще иска да стартира клъстер от Node.js процеси за справяне с товара.

В зависимост от това на кой сървър ще стартираме нашето приложение Koa, можем да имаме различен брой ядра.

Всяко ядро ​​ще отговаря за индивидуалното боравене с товара. По принцип всяка HTTP заявка ще бъде удовлетворена от едно ядро.

Така например - моята машина, която има осем ядра, ще обработва осем едновременни заявки.

Вече можем да преброим колко процесора имаме благодарение на osмодула:

В cpus()метода ще върне масив от обекти, които описват нашите процесори. Можем да обвържем дължината му с константа, която ще бъде наречена numWorkers, защото това е броят на работниците, които ще използваме.

Вече сме готови да изискваме clusterмодула.

Сега се нуждаем от начин за разделяне на нашия основен процес на Nотделни процеси.

Ще наречем нашия основен процес masterи другите процеси workers.

clusterМодулът Node.js предлага метод, наречен isMaster. Той ще върне булева стойност, която ще ни каже дали текущият процес се ръководи от работник или главен:

Страхотен. Златното правило тук е, че не искаме да обслужваме нашето приложение Koa в рамките на главния процес.

Искаме да създадем приложение Koa за всеки работник, така че когато постъпи заявка, първият безплатен работник ще се погрижи за него.

В cluster.fork()метода ще се побере нашата цел:

Добре, в началото това може да е малко сложно.

Както можете да видите в скрипта по-горе, ако нашият скрипт е изпълнен от главния процес, ще обявим константа, наречена workers. Това ще създаде работник за всяко ядро ​​на нашия процесор и ще съхрани цялата информация за тях.

Ако не се чувствате сигурни относно приетия синтаксис, използването […Array(x)].map()е точно същото като:

Просто предпочитам да използвам неизменни стойности, докато разработвам приложение с висока конкурентност.

Добавяне на Koa

Както казахме преди, не искаме да обслужваме нашето приложение Koa в рамките на главния процес.

Нека копираме нашата структура на приложението Koa в elseизявлението, за да сме сигурни, че тя ще бъде обслужвана от работник:

Както можете да видите, ние също добавихме няколко слушатели на събития в isMasterизявлението:

Първият ще ни каже, че е роден нов работник. Вторият ще създаде нов работник, когато друг работник се срине.

По този начин главният процес ще бъде отговорен само за създаването на нови работници и тяхното организиране. Всеки работник ще обслужва екземпляр на Коа, който ще бъде достъпен на :3000пристанището.

Тестване на десет едновременни заявки - с клъстериране

Както можете да видите, получихме първия си отговор след около 10 секунди, а последния след около 14 секунди. Това е невероятно подобрение спрямо предходните 40 секунди време за реакция!

Направихме десет едновременни заявки и сървърът на Koa взе осем от тях веднага. Когато първият работник изпрати отговора си на клиента, той взе една от останалите заявки и я обработи!

Заключение

Node.js има невероятен капацитет за обработка на големи товари, но не би било разумно да спрете заявка, докато сървърът не завърши процеса си.

Всъщност уеб сървърите Node.js могат да обработват хиляди едновременни заявки само ако незабавно изпратите отговор на клиента.

Най-добрата практика би била да добавите pub / sub интерфейс за съобщения, използвайки Redis или друг невероятен инструмент. Когато клиентът изпрати заявка, сървърът започва комуникация в реално време с други услуги. Това се грижи за скъпите работни места.

Балансировъците на товара също биха помогнали много за разделянето на големи натоварвания от трафика.

Отново технологията ни дава безкрайни възможности и ние със сигурност ще намерим правилното решение за мащабиране на нашето приложение до безкрайност и след това!