Pętla wydarzeń Nodejs

141

Czy w architekturze nodejs są wewnętrznie dwie pętle zdarzeń?

  • libev / libuv
  • v8 pętla zdarzeń javascript

Czy w żądaniu we / wy węzeł umieszcza w kolejce żądanie do libeio, które z kolei powiadamia o dostępności danych za pośrednictwem zdarzeń korzystających z libev, a na koniec te zdarzenia są obsługiwane przez pętlę zdarzeń v8 przy użyciu wywołań zwrotnych?

Zasadniczo, w jaki sposób libev i libeio są zintegrowane z architekturą nodejs?

Czy jest dostępna dokumentacja dająca jasny obraz wewnętrznej architektury nodejs?

Tamil
źródło

Odpowiedzi:

175

Osobiście czytałem kod źródłowy node.js i v8.

Podjąłem podobny problem, jak ty, kiedy próbowałem zrozumieć architekturę node.js w celu napisania natywnych modułów.

To, co tutaj publikuję, to moje zrozumienie node.js i może to być również trochę odbiegające od ścieżki.

  1. Libev to pętla zdarzeń, która faktycznie działa wewnętrznie w node.js w celu wykonywania prostych operacji pętli zdarzeń. Został napisany oryginalnie dla systemów * nix. Libev zapewnia prostą, ale zoptymalizowaną pętlę zdarzeń, na której działa proces. Możesz przeczytać więcej o libev tutaj .

  2. LibEio to biblioteka do asynchronicznego wykonywania danych wejściowych. Obsługuje deskryptory plików, procedury obsługi danych, gniazda itp. Możesz przeczytać więcej na ten temat tutaj .

  3. LibUv to warstwa abstrakcji na szczycie libeio, libev, c-ares (dla DNS) i iocp (dla systemu Windows asynchronous-io). LibUv wykonuje, obsługuje i zarządza wszystkimi zdarzeniami io i zdarzeniami w puli zdarzeń. (w przypadku puli wątków libeio). Powinieneś sprawdzić samouczek Ryana Dahla na temat libUv. To zacznie mieć dla ciebie więcej sensu, jeśli chodzi o samo działanie libUv, a potem zrozumiesz, jak działa node.js na szczycie libuv i v8.

Aby zrozumieć tylko pętlę zdarzeń JavaScript, należy rozważyć obejrzenie tych filmów

Aby zobaczyć, jak libeio jest używane z node.js do tworzenia modułów asynchronicznych, powinieneś zobaczyć ten przykład .

Zasadniczo to, co dzieje się w node.js, polega na tym, że pętla v8 uruchamia i obsługuje wszystkie części javascript, a także moduły C ++ [gdy działają one w głównym wątku (zgodnie z oficjalną dokumentacją node.js jest jednowątkowy)]. Gdy znajduje się poza głównym wątkiem, libev i libeio obsługują go w puli wątków, a libev zapewnia interakcję z główną pętlą. Z mojego rozumienia wynika, że ​​node.js ma 1 stałą pętlę zdarzeń: to jest pętla zdarzeń w wersji 8. Do obsługi zadań asynchronicznych w C ++ używa puli wątków [przez libeio i libev].

Na przykład:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

To, co pojawia się we wszystkich modułach, jest zwykle wywołaniem funkcji Taskw puli wątków. Po zakończeniu wywołuje AfterTaskfunkcję w głównym wątku. Podczas gdy Eio_REQUESTjest to procedura obsługi żądań, która może być strukturą / obiektem, którego motywem jest zapewnienie komunikacji między pulą wątków a głównym wątkiem.

ShrekOverflow
źródło
Poleganie na fakcie, że libuv używa wewnętrznie libev, jest dobrym sposobem, aby kod nie był wieloplatformowy. Powinieneś dbać tylko o publiczny interfejs libuv.
Raynos
1
@Raynos libuv ma na celu upewnienie się, że jego x-platfousing zawiera wiele bibliotek. Dobrze ? stąd używanie libuv jest dobrym pomysłem
ShrekOverflow
1
@Abhishek From Doc process.nextTick- W następnej pętli wokół pętli zdarzeń wywołaj to wywołanie zwrotne. To nie jest prosty alias do setTimeout (fn, 0), jest znacznie wydajniejszy. Do której pętli zdarzeń to się odnosi? Pętla zdarzeń V8?
Tamil
2
Zauważ, że libuv nie jest już zaimplementowany na libev .
strcat
4
Czy jest sposób, aby „zobaczyć” kolejkę tego wydarzenia? Chciałbym móc zobaczyć kolejność wywołań na stosie i zobaczyć, jak nowe funkcje są tam umieszczane, aby lepiej zrozumieć, co się dzieje ... czy jest jakaś zmienna, która mówi, co zostało przeniesione do kolejki zdarzeń?
tbarbe
20

Wygląda na to, że niektóre z omawianych podmiotów (np. Libev itp.) Straciły na znaczeniu ze względu na to, że minęło trochę czasu, ale myślę, że pytanie nadal ma duży potencjał.

Spróbuję wyjaśnić działanie modelu sterowanego zdarzeniami za pomocą abstrakcyjnego przykładu, w abstrakcyjnym środowisku UNIX, w kontekście Node, na dzień dzisiejszy.

Perspektywa programu:

  • Silnik skryptów rozpoczyna wykonywanie skryptu.
  • Za każdym razem, gdy napotkana zostanie operacja związana z procesorem, jest ona wykonywana w trybie inline (rzeczywista maszyna), w całości.
  • Za każdym razem, gdy napotkana zostanie operacja powiązana z we / wy, żądanie i jego program obsługi zakończenia są rejestrowane w `` maszynie zdarzeń '' (maszyna wirtualna)
  • Powtarzaj czynności w ten sam sposób, aż do zakończenia skryptu. Operacja związana z procesorem - wykonaj in-line, te związane z I / O, żądanie do maszyny jak powyżej.
  • Po zakończeniu operacji we / wy słuchacze są wywoływani z powrotem.

Mechanizm zdarzeń powyżej jest nazywany frameworkiem pętli zdarzeń libuv AKA. Node wykorzystuje tę bibliotekę do implementacji modelu programowania sterowanego zdarzeniami.

Perspektywa węzła:

  • Miej jeden wątek do obsługi środowiska wykonawczego.
  • Podnieś skrypt użytkownika.
  • Skompiluj do wersji natywnej [dźwignia v8]
  • Załaduj plik binarny i wskocz do punktu wejścia.
  • Skompilowany kod wykonuje działania związane z procesorem w linii, używając prymitywów programowania.
  • Wiele kodów związanych z operacjami we / wy i zegarem ma natywne opakowania. Na przykład sieciowe I / O.
  • Dlatego wywołania I / O są kierowane ze skryptu do mostów C ++, z uchwytem I / O i modułem obsługi zakończenia przekazywane jako argumenty.
  • Kod natywny wykonuje pętlę libuv. Uzyskuje pętlę, umieszcza w kolejce zdarzenie niskiego poziomu, które reprezentuje I / O, oraz natywne opakowanie wywołania zwrotnego w strukturę pętli libuv.
  • Kod natywny powraca do skryptu - w tej chwili żadne operacje wejścia / wyjścia nie są wykonywane!
  • Powyższe pozycje są powtarzane wiele razy, aż cały kod nie-we / wy zostanie wykonany, a cały kod we / wy zostanie zarejestrowany w bibliotece libuv.
  • Wreszcie, gdy w systemie nie ma już nic do wykonania, węzeł przekazuje kontrolę do libuv
  • libuv wkracza do akcji, przechwytuje wszystkie zarejestrowane zdarzenia, wysyła zapytanie do systemu operacyjnego, aby uzyskać ich funkcjonalność.
  • Te, które są gotowe do wejścia / wyjścia w trybie nieblokującym, są odbierane, wykonywane we / wy i wysyłane ich wywołania zwrotne. Jedna po drugiej.
  • Te, które nie są jeszcze gotowe (na przykład odczyt gniazda, dla którego drugi punkt końcowy jeszcze nic nie zapisał) będą nadal sondowane w systemie operacyjnym, dopóki nie będą dostępne.
  • Pętla wewnętrznie utrzymuje stale rosnący licznik czasu. Gdy aplikacja żąda odroczonego wywołania zwrotnego (na przykład setTimeout), ta wewnętrzna wartość licznika czasu jest wykorzystywana do obliczenia odpowiedniego czasu na uruchomienie wywołania zwrotnego.

Podczas gdy większość funkcji jest obsługiwana w ten sposób, niektóre (wersje asynchroniczne) operacji na plikach są wykonywane za pomocą dodatkowych wątków, dobrze zintegrowanych z libuv. Podczas gdy operacje we / wy w sieci mogą czekać w oczekiwaniu na zdarzenie zewnętrzne, takie jak odpowiedź innego punktu końcowego z danymi itp., Operacje na plikach wymagają trochę pracy od samego węzła. Na przykład, jeśli otworzysz plik i poczekasz, aż fd będzie gotowy z danymi, to się nie stanie, ponieważ nikt tak naprawdę nie czyta! Jednocześnie, jeśli czytasz z pliku w linii w głównym wątku, może to potencjalnie blokować inne działania w programie i może powodować widoczne problemy, ponieważ operacje na plikach są bardzo powolne w porównaniu z działaniami związanymi z procesorem. Dlatego wewnętrzne wątki robocze (konfigurowane przez zmienną środowiskową UV_THREADPOOL_SIZE) są wykorzystywane do operacji na plikach,

Mam nadzieję że to pomoże.

Gireesh Punathil
źródło
Skąd wiedziałeś o tych rzeczach, czy możesz wskazać mi źródło?
Suraj Jain
18

Wprowadzenie do libuv

Projekt node.js rozpoczął się w 2009 roku jako środowisko JavaScript oddzielone od przeglądarki. Korzystając z Google V8 i libev Marca Lehmanna , node.js połączył model I / O - zdarzenie - z językiem, który był dobrze dopasowany do stylu programowania; ze względu na sposób, w jaki zostały ukształtowane przez przeglądarki. Wraz ze wzrostem popularności node.js ważne było, aby działał w systemie Windows, ale libev działał tylko w systemie Unix. Odpowiednikiem systemu Windows mechanizmów powiadamiania o zdarzeniach jądra, takich jak kqueue lub (e) poll, jest IOCP. libuv był abstrakcją wokół libev lub IOCP, w zależności od platformy, dostarczając użytkownikom API oparte na libev. W wersji node-v0.9.0 libuv libev została usunięta .

Również jedno zdjęcie, które opisuje pętlę zdarzeń w Node.js autorstwa @ BusyRich


Aktualizacja 05/09/2017

Zgodnie z tym dokumentem pętla zdarzeń Node.js ,

Poniższy diagram przedstawia uproszczony przegląd kolejności operacji w pętli zdarzeń.

   ┌───────────────────────┐
┌─>│        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐         incoming:   
           poll          │<─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘

uwaga: każde pole będzie określane jako „faza” pętli zdarzeń.

Przegląd faz

  • timery : ta faza wykonuje wywołania zwrotne zaplanowane do setTimeout()i setInterval().
  • Wywołania zwrotne we / wy : wykonuje prawie wszystkie wywołania zwrotne z wyjątkiem bliskich wywołań zwrotnych , zaplanowanych przez zegary i setImmediate().
  • bezczynny, przygotuj : używany tylko wewnętrznie.
  • poll : pobierz nowe zdarzenia I / O; W razie potrzeby węzeł zostanie tutaj zablokowany.
  • check : setImmediate()tutaj wywoływane są callbacki.
  • blisko callbacków : np socket.on('close', ...).

Pomiędzy każdym uruchomieniem pętli zdarzeń Node.js sprawdza, czy oczekuje na asynchroniczne we / wy lub zegary i zamyka czysto, jeśli ich nie ma.

zangw
źródło
Zacytowałeś to „ In the node-v0.9.0 version of libuv libev was removed”, ale nie ma na ten temat opisu w nodejs changelog. github.com/nodejs/node/blob/master/CHANGELOG.md . A jeśli libev zostanie usunięte, to w jaki sposób asynchroniczne operacje wejścia / wyjścia są wykonywane w nodejs?
intekhab
@intekhab, na ten link myślę, że libuv oparty na libeio może być użyty jako pętla zdarzeń w node.js.
zangw,
@intekhab myślę, że libuv implementuje wszystkie funkcje związane z we / wy i odpytywaniem. tutaj sprawdź w tym dokumencie: docs.libuv.org/en/v1.x/loop.html
mohit kaushik
13

W architekturze NodeJs istnieje jedna pętla zdarzeń.

Model pętli zdarzeń Node.js.

Aplikacje węzłów działają w modelu opartym na zdarzeniach jednowątkowych. Jednak Node implementuje pulę wątków w tle, aby można było wykonać pracę.

Node.js dodaje pracę do kolejki zdarzeń, a następnie odbiera ją w pojedynczym wątku z uruchomioną pętlą zdarzeń. Pętla zdarzeń przechwytuje najwyższy element w kolejce zdarzeń, wykonuje go, a następnie przechwytuje następny element.

Podczas wykonywania kodu, który jest dłuższy lub ma blokujące operacje we / wy, zamiast wywoływać funkcję bezpośrednio, dodaje funkcję do kolejki zdarzeń wraz z wywołaniem zwrotnym, które zostanie wykonane po zakończeniu funkcji. Po wykonaniu wszystkich zdarzeń w kolejce zdarzeń Node.js aplikacja Node.js zostaje zakończona.

Pętla zdarzeń zaczyna powodować problemy, gdy nasze funkcje aplikacji blokują się na I / O.

Node.js używa wywołań zwrotnych zdarzeń, aby uniknąć konieczności czekania na blokowanie operacji we / wy. Dlatego wszelkie żądania, które wykonują blokujące operacje we / wy, są wykonywane w innym wątku w tle.

Gdy zdarzenie blokujące operacje we / wy jest pobierane z kolejki zdarzeń, Node.js pobiera wątek z puli wątków i wykonuje tam funkcję zamiast w głównym wątku pętli zdarzeń. Zapobiega to wstrzymywaniu przez blokujące we / wy pozostałych zdarzeń w kolejce zdarzeń.

Peter Hauge
źródło
8

Libuv udostępnia tylko jedną pętlę zdarzeń, V8 to tylko silnik wykonawczy JS.

Warren Zhou
źródło
1

Jako początkujący javascript miałem te same wątpliwości, czy NodeJS zawiera 2 pętle zdarzeń? Po długich badaniach i dyskusjach z jednym z współautorów V8 otrzymałem następujące koncepcje.

  • Pętla zdarzeń to podstawowa abstrakcyjna koncepcja modelu programowania JavaScript. Silnik V8 zapewnia więc domyślną implementację pętli zdarzeń, którą programy osadzające (przeglądarka, węzeł) mogą zastąpić lub rozszerzyć . Możecie znaleźć domyślną implementację pętli zdarzeń w V8 tutaj
  • W NodeJS istnieje tylko jedna pętla zdarzeń , która jest udostępniana przez środowisko wykonawcze węzła. Domyślna implementacja pętli zdarzeń V8 została zastąpiona implementacją pętli zdarzeń NodeJS
arunjos007
źródło
0

pbkdf2Funkcja ma wdrożenie JavaScript ale faktycznie delegaci wszystkich do zrobienia w C ++ stronie.

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

zasób: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

Moduł Libuv ma inną odpowiedzialność, która jest istotna dla niektórych bardzo szczególnych funkcji w bibliotece standardowej.

W przypadku niektórych wywołań funkcji biblioteki standardowej strona Node C ++ i Libuv decydują się całkowicie na wykonanie kosztownych obliczeń poza pętlą zdarzeń.

Zamiast tego używają czegoś, co nazywa się pulą wątków, pula wątków to seria czterech wątków, których można używać do wykonywania zadań wymagających dużej mocy obliczeniowej, takich jak pbkdf2funkcja.

Domyślnie Libuv tworzy 4 wątki w tej puli wątków.

Oprócz wątków używanych w pętli zdarzeń istnieją cztery inne wątki, których można użyć do odciążenia kosztownych obliczeń, które muszą być wykonywane w naszej aplikacji.

Wiele funkcji zawartych w standardowej bibliotece Node automatycznie korzysta z tej puli wątków. pbkdf2Funkcja jest jednym z nich.

Obecność tej puli wątków jest bardzo znacząca.

Tak więc Node nie jest naprawdę jednowątkowy, ponieważ istnieją inne wątki, których Node używa do wykonywania niektórych zadań wymagających dużej mocy obliczeniowej.

Gdyby pula zdarzeń była odpowiedzialna za wykonanie kosztownego obliczeniowo zadania, nasza aplikacja Node nie mogła zrobić nic innego.

Nasz procesor uruchamia wszystkie instrukcje w wątku jeden po drugim.

Korzystając z puli wątków, możemy robić inne rzeczy wewnątrz pętli zdarzeń podczas wykonywania obliczeń.

Daniel
źródło