Żądania Node.js i CPU intensywne

215

Zacząłem majstrować przy serwerze HTTP Node.js i naprawdę lubię pisać Javascript po stronie serwera, ale coś powstrzymuje mnie od rozpoczęcia używania Node.js do mojej aplikacji internetowej.

Rozumiem całą koncepcję asynchronicznych operacji we / wy, ale jestem nieco zaniepokojony przypadkowymi przypadkami, w których kod proceduralny wymaga dużej mocy obliczeniowej, np. Manipulowanie obrazem lub sortowanie dużych zestawów danych.

Jak rozumiem, serwer będzie bardzo szybki w przypadku prostych żądań stron internetowych, takich jak przeglądanie listy użytkowników lub przeglądanie postów na blogu. Jeśli jednak chcę napisać kod bardzo obciążający procesor (na przykład w panelu administracyjnym), który generuje grafikę lub zmienia rozmiar tysięcy obrazów, żądanie będzie bardzo wolne (kilka sekund). Ponieważ ten kod nie jest asynchroniczny, wszystkie żądania przychodzące do serwera w ciągu tych kilku sekund będą blokowane, dopóki moje wolne żądanie nie zostanie wykonane.

Jedną z sugestii było użycie Web Workers do zadań intensywnie wykorzystujących procesor. Obawiam się jednak, że pracownicy sieci utrudnią napisanie czystego kodu, ponieważ działa on poprzez dołączenie osobnego pliku JS. Co się stanie, jeśli kod intensywny procesora znajduje się w metodzie obiektu? To trochę wkurza, aby napisać plik JS dla każdej metody wymagającej dużej mocy obliczeniowej.

Inną sugestią było odrodzenie procesu potomnego, ale czyni to kod jeszcze trudniejszym do utrzymania.

Jakieś sugestie, jak pokonać tę (postrzeganą) przeszkodę? Jak piszesz czysty obiektowy kod za pomocą Node.js, upewniając się, że duże zadania procesora są wykonywane asynchronicznie?

Olivier Lalonde
źródło
2
Olivier, zadałeś identyczne pytanie, które miałem na myśli (nowy w węźle), a konkretnie w odniesieniu do przetwarzania obrazów. W Javie mogę korzystać ze sztywnego wątku ExecutorService i przekazywać mu wszystkie zadania zmiany rozmiaru i czekać na zakończenie z całego połączenia, w węźle nie wymyśliłem, jak odrzucić pracę do zewnętrznego modułu, który ogranicza ( powiedzmy) maksymalna liczba jednoczesnych operacji do 2 na raz. Czy znalazłeś elegancki sposób na zrobienie tego?
Riyad Kalla,

Odpowiedzi:

55

Potrzebujesz kolejki zadań! Przenoszenie długoterminowych zadań z serwera WWW jest DOBRA. Przechowywanie każdego zadania w „osobnym” pliku js promuje modułowość i ponowne użycie kodu. Zmusza cię do myślenia o tym, jak ustrukturyzować swój program w sposób, który ułatwi debugowanie i utrzymanie na dłuższą metę. Kolejną zaletą kolejki zadań jest to, że pracownicy mogą być pisani w innym języku. Po prostu pop zadanie, wykonaj pracę i odpisz odpowiedź.

coś takiego: https://github.com/resque/resque

Oto artykuł z github o tym, dlaczego go zbudowali http://github.com/blog/542-introducing-resque

Tim
źródło
35
Dlaczego łączysz się z bibliotekami Ruby w pytaniu konkretnie uziemionym w świecie węzłów?
Jonathan Dumaine
1
@JonathanDumaine To dobra implementacja kolejki zadań. Opróżnij kod ruby ​​i przepisz go w javascript. ZYSK!
Simon Stender Boisen
2
Jestem wielkim fanem gearmana, pracownicy gearmana nie odpytują serwera gearmana o nowe zadania - nowe zadania są natychmiast przekazywane pracownikom. Bardzo responsywny
Casey Flynn
1
W rzeczywistości ktoś przeniósł go do świata węzłów: github.com/technoweenie/coffee-resque
FrontierPsycho
@pacerier, dlaczego tak mówisz? Co proponujesz?
luis.espinal
289

Jest to nieporozumienie z definicją serwera WWW - należy go używać tylko do „rozmowy” z klientami. Zadania o dużym obciążeniu powinny być delegowane do samodzielnych programów (które oczywiście można również zapisać w JS).
Zapewne powiedziałbyś, że jest brudny, ale zapewniam cię, że proces serwera WWW tkwiący w zmianie rozmiaru obrazów jest po prostu gorszy (nawet dla powiedzmy Apache, gdy nie blokuje innych zapytań). Mimo to możesz użyć wspólnej biblioteki, aby uniknąć nadmiarowości kodu.

EDYCJA: Wymyśliłem analogię; aplikacja internetowa powinna być jak restauracja. Masz kelnerów (serwer WWW) i kucharzy (pracowników). Kelnerzy mają kontakt z klientami i wykonują proste zadania, takie jak zapewnienie menu lub wyjaśnienie, czy danie jest wegetariańskie. Z drugiej strony trudniejsze zadania przekazują do kuchni. Ponieważ kelnerzy robią tylko proste rzeczy, reagują szybko, a kucharze mogą skoncentrować się na swojej pracy.

Node.js byłby tutaj jednym, ale bardzo utalentowanym kelnerem, który może przetwarzać wiele żądań na raz, a Apache byłby gangiem głupich kelnerów, którzy przetwarzają tylko jedno żądanie. Gdyby ten kelner Node.js zaczął gotować, byłaby to natychmiastowa katastrofa. Mimo to gotowanie może wyczerpać nawet dużą liczbę kelnerów Apache, nie wspominając o chaosie w kuchni i postępującym spadku wrażliwości.

mbq
źródło
6
Cóż, w środowisku, w którym serwery WWW są wielowątkowe lub wieloprocesowe i mogą obsługiwać więcej niż jedno współbieżne żądanie, bardzo często spędza się kilka sekund na pojedynczym żądaniu. Ludzie zaczęli się tego spodziewać. Powiedziałbym, że nieporozumieniem jest to, że node.js jest „zwykłym” serwerem WWW. Korzystając z node.js, trzeba nieco dostosować model programowania, co obejmuje wypychanie „długotrwałych” ćwiczeń do jakiegoś asynchronicznego pracownika.
Thilo,
13
Nie spawnuj procesu potomnego dla każdego żądania (które przeczy celowi node.js). Odradzaj pracowników z wnętrza tylko twoich ciężkich próśb. Lub skieruj swoją ciężką pracę w tle na coś innego niż node.js.
Thilo,
47
Dobra analogia, Mbq!
Lance Fisher
6
Ha, naprawdę to lubię. „Node.js: powodowanie, że złe praktyki działają źle”
Ethan
7
@mbq Podoba mi się analogia, ale przydałoby się trochę pracy. Tradycyjny model wielowątkowy byłby osobą, która jest zarówno kelnerem, jak i kucharzem. Po przyjęciu zamówienia osoba ta musi wrócić i ugotować posiłek, zanim będzie w stanie zrealizować kolejne zamówienie. Model node.js ma węzły jako kelnerzy, a pracownicy sieci - kucharze. Kelnerzy obsługują pobieranie / rozwiązywanie żądań, podczas gdy pracownicy zarządzają bardziej czasochłonnymi zadaniami. Jeśli chcesz zwiększyć skalę, po prostu zmień główny serwer w klaster węzłów i odwróć proxy wymagające zadań procesora na inne serwery zbudowane do przetwarzania wielowątkowego.
Evan Plaice
16

Nie chcesz, aby kod intensywnie wykorzystujący procesor był wykonywany asynchronicznie, chcesz, aby był wykonywany równolegle . Musisz przerwać przetwarzanie z wątku obsługującego żądania HTTP. To jedyny sposób na rozwiązanie tego problemu. W przypadku NodeJS odpowiedzią jest moduł klastra, do odradzania procesów potomnych w celu wykonania dużego podnoszenia. (Węzeł AFAIK nie ma pojęcia wątków / pamięci współdzielonej; są to procesy lub nic). Istnieją dwie opcje strukturyzacji aplikacji. Możesz uzyskać rozwiązanie 80/20, odradzając 8 serwerów HTTP i synchronicznie obsługując zadania wymagające intensywnych obliczeń na procesach potomnych. Jest to dość proste. Możesz poświęcić godzinę na przeczytanie o tym pod tym linkiem. W rzeczywistości, jeśli tylko zerwiesz przykładowy kod na górze tego linku, dostaniesz się w 95%.

Innym sposobem skonfigurowania tego jest skonfigurowanie kolejki zadań i wysyłanie dużych zadań obliczeniowych przez kolejkę. Zauważ, że z IPC wiąże się wiele narzutów dla kolejki zadań, więc jest to użyteczne tylko wtedy, gdy zadania są znacznie większe niż narzut.

Dziwi mnie, że żadna z tych odpowiedzi nie wspomina nawet o klastrze.

Tło: Kod asynchroniczny to kod, który zawiesza się, dopóki coś się nie wydarzy gdzie indziej , w którym to momencie kod budzi się i kontynuuje wykonywanie. Jednym z bardzo częstych przypadków, w których coś powolnego musi się wydarzyć gdzie indziej, jest I / O.

Kod asynchroniczny nie jest użyteczny, jeśli procesor jest odpowiedzialny za wykonanie pracy. Dokładnie tak jest w przypadku zadań „intensywnych obliczeniowych”.

Teraz może się wydawać, że kod asynchroniczny jest niszowy, ale w rzeczywistości jest bardzo powszechny. Zdarza się, że nie jest użyteczny przy intensywnych zadaniach obliczeniowych.

Oczekiwanie na We / Wy to przykład, który zawsze zdarza się na serwerach WWW. Każdy klient, który łączy się z serwerem, otrzymuje gniazdo. Przez większość czasu gniazda są puste. Nie chcesz nic robić, dopóki gniazdo nie otrzyma danych, w którym to momencie chcesz obsłużyć żądanie. Pod maską serwer HTTP, taki jak Node, korzysta z biblioteki zdarzeń (libev) do śledzenia tysięcy otwartych gniazd. System operacyjny powiadamia libev, a następnie libev powiadamia NodeJS, gdy jedno z gniazd pobiera dane, a następnie NodeJS umieszcza zdarzenie w kolejce zdarzeń, a kod http uruchamia się w tym momencie i obsługuje zdarzenia jeden po drugim. Zdarzenia nie są umieszczane w kolejce, dopóki gniazdo nie ma danych, więc zdarzenia nigdy nie czekają na dane - już na nie są.

Jednowątkowe serwery oparte na zdarzeniach mają sens jako paradygmat, gdy wąskie gardło czeka na większość pustych połączeń gniazdowych i nie potrzebujesz całego wątku lub procesu dla każdego bezczynnego połączenia i nie chcesz sondować 250k gniazda, aby znaleźć następny z danymi.

masonk
źródło
powinna być poprawna odpowiedź .... jeśli chodzi o rozwiązanie, w którym spawnujesz 8 klastrów, potrzebujesz 8 rdzeni, prawda? Lub moduł równoważenia obciążenia z wieloma serwerami.
Muhammad Umer
także co jest dobrym sposobem, aby dowiedzieć się o 2. rozwiązaniu, ustawianiu kolejki. Pojęcie kolejki jest dość proste, ale stanowi część przesyłania komunikatów między procesami a zagraniczną kolejką.
Muhammad Umer
Zgadza się. Musisz jakoś przenieść pracę na inny rdzeń. Do tego potrzebujesz innego rdzenia.
masonk
Re: kolejki. Praktyczną odpowiedzią jest użycie kolejki zadań. Istnieje kilka dostępnych dla węzła. Nigdy nie korzystałem z żadnego z nich, więc nie mogę wydać rekomendacji. Ciekawostką jest to, że procesy robocze i procesy kolejkowe ostatecznie będą komunikować się przez gniazda.
masonk
7

Kilka podejść, których możesz użyć.

Jak zauważa @Tim, możesz utworzyć zadanie asynchroniczne, które będzie znajdować się na zewnątrz lub równolegle do głównej logiki obsługi. Zależy od twoich dokładnych wymagań, ale nawet cron może działać jako mechanizm kolejkowania.

Pracownicy sieci Web mogą pracować dla procesów asynchronicznych, ale obecnie nie są obsługiwane przez node.js. Istnieje kilka rozszerzeń zapewniających wsparcie, na przykład: http://github.com/cramforce/node-worker

Nadal możesz nadal używać modułów i kodu za pomocą standardowego mechanizmu „wymaga”. Musisz tylko upewnić się, że początkowa wysyłka do pracownika przekazuje wszystkie informacje potrzebne do przetworzenia wyników.

Toby Hede
źródło
0

Zastosowanie child_processjest jednym rozwiązaniem. Ale każdy spawnowany proces potomny może zużywać dużo pamięci w porównaniu do Gogoroutines

Możesz także użyć rozwiązania opartego na kolejce, takiego jak kue

neo
źródło