🔥

Тред (Павел Лосев)


Второй темой будет многопоточность в Node.js. Как известно, изначально рантайм работает в одном потоке, используя только одно ядро процессора. В 90% случаев одного ядра хватает, но когда у вас хайлоад API, и ~45K req/s не хватает, то можно использовать мультитрединг.

Из встроенных модулей самым подходящим для веб приложений будет cluster. Суть cluster в том, что он позволяет запускать программу на JavaScript в нескольких процессах, работая на более чем одном ядре процессора.

Внутри себя использует child_process.fork() для создания форк-процессов и работает по алгоритму Round Robin (это когда есть очередь процессов, где каждый процесс запускается и находится в очереди пока не выполнится). Ссылка на реализацию внутри самой ноды: github.com/nodejs/node/bl…

В backend приложениях без БД, cluster легко интегрировать, нужно сделать проверку на то, что процесс является мастером - и если да, то сделать форк-процесс N раз (где N это число ядер), а если это не мастер то запускать это самое приложение. Но у тредов есть подводные камни...

Один из самых известных - синхронизация. Проблема в том, что потоки работают ассинхронно, а доступ к значениям происходит синхронно. Или несколько потоков могут сделать одно и тоже, например дважды сделать запись в БД. Из-за этого ваше приложение становится непредсказуемым.

Одно из решений - mutex. Они блокируют поток в критическом месте, и позже разблокируют его, чтобы ничего не поломать. В C++ например, есть std::mutex, а в JavaScript есть огромное количество npm пакетов, которые по-разному mutex реализуют: Mutexify, Async Mutex

ссылки: Mutexify: npmjs.com/package/mutexi…, Async Mutex: github.com/DirtyHairy/asy…

Дело в том, что mutex'ы полезны только когда у процессов есть общая память. Общая память у процессов в другом модуле - в worker_threads. В cluster'е у каждого своя собственная, поэтому нужно использовать что-то более глобальное.

Т.к. в cluster у каждого процесса своя память, то нужно использовать распределённое блокирование (distributed lock). Если вкратце, то распределенная блокировка - это механизм, который обеспечивает управление потоком в контексте, в котором действуют более чем один процесс.

Необязательно это должен быть многопоточный сервер, ещё один юзкейс это когда у вас много инстансов бэка запущены на разных машинах, но используют одну и ту же БД. Пример реализации distributed lock на Mongoose: github.com/coyotte508/mon….

Из простых решений для кластеризации Node.js приложений (без использования cluster в коде, и не учитывая БД) можно использовать менеджеры процессов, например pm2 (pm2.io). Он использует cluster под капотом. Ещё недавно узнал про forever (github.com/foreversd/fore…).

Мультитрединг - довольно обширная тема, в которой у меня есть небольшие знания. Недавно я был в IT лагере (goto.msk.ru) (я там был, дважды), у нас был курс по lowlevel, и там ещё был мультитрединг на C/C++. Часть этих полученных знаний я применил в этом треде.

Я мог перепутать термины - речь в треде идёт об использовании потоков (форк-процессов (надеюсь я хотя бы это правильно назвал)) в Node.js, и как их синхронизировать в бекенд приложениях Если я где-то ещё перепутал - киньте в реплаи правку и я сделаю ретвит

@jsunderhood В результате вызова cluster.fork() создаётся процесс, а не поток. Отличие в том, что процессы друг от друга изолированы. Cluster с воркерами и несколько отдельных серверов за nginx - принципиально одно и то же: воркеры не делят память, обмениваются между собой через IPC.
В самом первом твите ошибка - то что описывается ниже это не мультитрединг. Это процессная многозадачность (поправьте если ещё раз ошибся) - в cluster спавнятся не потоки, а отдельные процессы со своей памятью. Общая память в worker_threads twitter.com/maxatwork/stat…

(сделал ретвит правки с моим комментарием если вдруг кто не так понял)

@jsunderhood У worker_threads не общая память, а возможность работать с shared memory для коммуникации. Фактически же это те же самые отдельные копии node.js процессов, живущие каждый в своём изолированном мире.
ещё одна ошибка прямо в этом твите))) в worker_threads не общая память, а shared memory twitter.com/amel_true/stat…

Поправка - не общая память, а память, которой можно обмениваться через SharedArrayBuffer