"Nie blokuj pętli zdarzeń..." - zapewne słyszałeś to zdanie wiele razy... Nie dziwię się, ponieważ jest to jedno z najważniejszych założeń podczas pracy z Node. Ale jest też druga "rzecz", której nie należy blokować - Worker Pool. Jej zaniedbanie może mieć znaczący wpływ na wydajność aplikacji, a nawet jej bezpieczeństwo.
Nici
Najważniejsza rzecz do zapamiętania: istnieją dwa rodzaje wątków w Node.js: Główny wątek - który jest obsługiwany przez Pętla zdarzeńoraz Pula pracowników (pula wątków) - która jest pulą wątków -
dzięki libuv. Każdy z nich ma inne zadanie. Celem pierwszego z nich jest obsługa nieblokujących operacji wejścia/wyjścia, a drugi jest odpowiedzialny za intensywną pracę procesora, a także blokowanie operacji wejścia/wyjścia.

Ale czym jest wątek i czym różni się od procesu? Istnieje kilka różnic, ale najważniejszą z nich jest my jest sposób przydzielania im pamięci. O procesie można myśleć jak o aplikacji. Wewnątrz każdego procesu znajduje się fragment pamięci przeznaczony tylko dla niego. Tak więc jeden proces nie ma dostępu do pamięci drugiego, a ta właściwość zapewnia wysokie bezpieczeństwo. Aby ustanowić komunikację między nimi, musimy wykonać pewną pracę. Wątki są inne. Wątki działają wewnątrz procesu i współdzielą tę samą pamięć, więc nie ma żadnego problemu z ich współdzieleniem dane.
Jednak jedna kwestia powoduje problem. Jest to tzw. warunek wyścigu. Wątki mogą działać w tym samym czasie, więc skąd mamy wiedzieć, który kończy się pierwszy? Może się zdarzyć, że przy pierwszym uruchomieniu pierwsza operacja zakończy się jako pierwsza, a następnym razem może być odwrotnie i druga operacja zakończy się przed pierwszą. Wyobraź sobie pracę z operacjami zapisu/odczytu w takich warunkach! Koszmar! Czasami bardzo trudno jest napisać poprawne kod w środowisku wielowątkowym.
Ponadto języki wielowątkowe mają duży narzut pamięciowy, ponieważ tworzą osobny wątek dla każdego żądania; więc jeśli chcesz wywołać 1000 żądań, tworzą 1000 wątków.
Jak poradzić sobie z takim problemem? Zamiast tego użyć pojedynczego wątku! I to jest właśnie to Węzeł oferuje.

Jako JavaScript deweloper Zachęcam do obejrzenia film
w którym Bart Belder jasno wyjaśnia koncepcję pętli zdarzeń. Powyższy diagram pochodzi z jego prezentacji. A jeśli w ogóle nie znasz tych terminów, zarówno Węzeł Libuv i Libuv mają doskonałą dokumentację 🙂
O blokowaniu
W Rozwój JavaScript mówią, że ponieważ Węzeł jest jednowątkowa i nieblokująca, można osiągnąć wyższą współbieżność przy tych samych zasobach niż w przypadku rozwiązań wielowątkowych. To prawda, ale nie jest to tak piękne i łatwe, jak mogłoby się wydawać.
Od Node.js jest jednowątkowa (JS ), zadania wymagające dużej mocy obliczeniowej procesora będą blokować wszystkie trwające żądania do czasu zakończenia danego zadania. Tak więc prawdą jest, że w Node.js można zablokować każde żądanie tylko dlatego, że jedno z nich zawierało instrukcję blokującą. Kod blokujący oznacza, że jego zakończenie zajmuje więcej niż kilka milisekund. Nie należy jednak mylić długiego czasu odpowiedzi z blokowaniem. Odpowiedź z bazy danych może trwać bardzo długo, ale nie blokuje procesu (aplikacji).
Metody blokujące są wykonywane synchronicznie, a metody nieblokujące są wykonywane asynchronicznie.
Jak spowolnić (lub zablokować) pętlę zdarzeń?
- podatne wyrażenie regularne - podatne wyrażenie regularne to takie, na którym silnik wyrażeń regularnych może zająć wykładniczy czas; możesz przeczytać o nich więcej tutaj,
- Operacje JSON na dużych obiektach,
- przy użyciu synchronicznych interfejsów API z Węzeł zamiast wersji asynchronicznych; wszystkie metody I/O w bibliotece standardowej Node.js zapewniają również ich wersje asynchroniczne,
- inne błędy programistyczne, takie jak synchroniczne nieskończone pętle.
W takim przypadku, skoro Worker Pool korzysta z puli wątków, czy możliwe jest ich zablokowanie? Niestety tak 🙁 Węzeł opiera się na filozofii jeden wątek dla wielu klientów. Załóżmy, że dane zadanie wykonywane przez konkretnego Workera jest bardzo złożone i wymaga więcej czasu na jego ukończenie. W rezultacie Worker zostaje zablokowany i nie można go użyć do wykonania żadnego z innych oczekujących zadań, dopóki jego instrukcje nie zostaną wykonane. Jak już zapewne się domyśliłeś, może to mieć wpływ na wydajność. Możesz zapobiec takim problemom, minimalizując różnice w czasach zadań za pomocą partycjonowania zadań.
Wnioski
Unikaj blokowania, to na pewno. Jeśli tylko możesz, zawsze wybieraj asynchroniczne wersje standardowych API bibliotek. W przeciwnym razie po uruchomieniu aplikacji klient może doświadczyć kilku problemów, zaczynając od obniżonej przepustowości, a kończąc na całkowitym wycofaniu, co jest fatalne z punktu widzenia użytkownika.
Czytaj więcej:
Dlaczego (prawdopodobnie) powinieneś używać Typescript
Jak nie zabić projektu złymi praktykami kodowania?
Strategie pobierania danych w NextJS