Node.js
Серверный JavaScript на Node.js — где он уместен, потоки и backpressure, многоядерная конкурентность и graceful shutdown.
5 вопросов
MiddleТеорияЧастоКакие типы потоков есть в Node.js и какую проблему они решают?
Какие типы потоков есть в Node.js и какую проблему они решают?
Потоки обрабатывают данные по частям вместо загрузки всего в память, удерживая её расход ровным для больших или неограниченных данных. Есть четыре типа: Readable (источник, например чтение файла), Writable (приёмник, например запись файла), Duplex (оба, например TCP-сокет) и Transform (Duplex, изменяющий чанки, например gzip). pipe соединяет их и пробрасывает backpressure, чтобы быстрый источник не захлестнул медленный приёмник.
Типичные ошибки
- ✗Читать весь большой файл в память вместо потоковой обработки по частям
- ✗Игнорировать backpressure, позволяя быстрому источнику переполнить медленный writable
- ✗Путать Transform с Duplex — Transform изменяет чанки по мере прохождения
Уточняющие вопросы
- →Как
pipeпробрасывает backpressure между readable и writable? - →Когда выбрать Transform-поток вместо ручной буферизации и преобразования?
JuniorТеорияИногдаКогда Node.js хорошо подходит, а когда плохо?
Когда Node.js хорошо подходит, а когда плохо?
Node.js хорош для I/O-нагрузок — API, прокси, real-time приложения — потому что его однопоточный event loop обслуживает тысячи одновременных соединений, не блокируясь, пока ждёт сеть или диск. Он плохо подходит для CPU-нагрузок (тяжёлые вычисления, обработка изображений/видео): длинная синхронная задача блокирует event loop и тормозит все прочие запросы, пока не завершится.
Типичные ошибки
- ✗Запускать CPU-тяжёлую синхронную работу в главном потоке, блокируя event loop
- ✗Думать, что Node по умолчанию многопоточен для прикладного кода
- ✗Считать, что конкурентность требует одного OS-потока на соединение
Уточняющие вопросы
- →Как не дать CPU-тяжёлой задаче заблокировать event loop?
- →Почему одна медленная синхронная функция влияет на все ожидающие запросы?
MiddleТеорияИногдаВ Node.js чем worker_threads отличается от child_process для использования многих ядер?
В Node.js чем worker_threads отличается от child_process для использования многих ядер?
Оба добавляют параллелизм сверх единственного главного потока. worker_threads выполняют JS в потоках внутри того же процесса, дёшево разделяя память через SharedArrayBuffer и общаясь с малыми накладными расходами — идеально для CPU-нагрузки. child_process (spawn/fork/exec) запускает отдельные OS-процессы с изолированной памятью, общаясь через IPC или пайпы — лучше для запуска других программ или полной изоляции. Для раскладки HTTP-сервера по ядрам используйте cluster.
Типичные ошибки
- ✗Ожидать, что
worker_threadsделят обычные переменные безSharedArrayBufferили сообщений - ✗Хвататься за
child_processдля CPU-работы, где внутрипроцессные потоки дешевле - ✗Думать, что Node может использовать много ядер без worker'ов, cluster или дочерних процессов
Уточняющие вопросы
- →Когда изоляция дочернего процесса стоит его более дорогой коммуникации?
- →Как модуль cluster раскладывает входящие соединения по ядрам?
MiddleТеорияИногдаКаковы фазы event loop в Node.js и где выполняются setImmediate и process.nextTick?
Каковы фазы event loop в Node.js и где выполняются setImmediate и process.nextTick?
Event loop libuv проходит упорядоченные фазы: timers (setTimeout), pending callbacks, poll (I/O), check (setImmediate) и close callbacks. Между каждым колбэком он опустошает две очереди микрозадач: сначала process.nextTick, затем колбэки промисов.
Типичные ошибки
- ✗Считать
setImmediateидентичнымsetTimeout(fn, 0) - ✗Думать, что микрозадачи промисов выполняются раньше
process.nextTick - ✗Ожидать детерминированного порядка
setTimeout(0)противsetImmediateна верхнем уровне
Уточняющие вопросы
- →Внутри I/O-колбэка почему
setImmediateнадёжно обгоняетsetTimeout(0)? - →Как рекурсивный
process.nextTickможет заморить голодом фазу I/O (poll)?
MiddleТеорияРедкоЧто такое graceful shutdown в Node.js и почему это важно?
Что такое graceful shutdown в Node.js и почему это важно?
Graceful shutdown означает, что при сигнале завершения (SIGTERM/SIGINT) процесс перестаёт принимать новую работу, доводит до конца запросы в полёте, закрывает ресурсы (пулы БД, сокеты, HTTP-сервер), затем выходит. Это важно, потому что резкий выход обрывает живые запросы, течёт соединениями и может портить незавершённые записи. Таймаут принудительно завершает процесс, если очистка зависла, чтобы застрявшее соединение не блокировало выход навсегда.
Типичные ошибки
- ✗Выходить по
SIGTERMсразу, обрывая ещё обслуживаемые запросы - ✗Забыть закрыть пулы БД и сокеты, утекая соединениями при передеплоях
- ✗Опустить таймаут принудительного выхода, из-за чего зависшее соединение блокирует завершение навсегда
Уточняющие вопросы
- →Зачем нужен таймаут принудительного выхода рядом с чистым путём завершения?
- →Какой сигнал шлёт оркестратор вроде Kubernetes перед убийством пода?