Цикл событий Nodejs

143

Есть ли в архитектуре nodejs два внутренних цикла событий?

  • libev / libuv
  • Цикл событий javascript v8

В запросе ввода-вывода узел ставит в очередь запрос к libeio, который, в свою очередь, уведомляет о доступности данных через события, используя libev, и, наконец, эти события обрабатываются циклом событий v8 с использованием обратных вызовов?

В принципе, как libev и libeio интегрированы в архитектуру nodejs?

Есть ли какая-либо документация, дающая четкое представление о внутренней архитектуре nodejs?

Тамильский
источник

Ответы:

175

Я лично читал исходный код node.js & v8.

Я столкнулся с подобной проблемой, как и вы, когда пытался понять архитектуру node.js, чтобы писать собственные модули.

То, что я публикую здесь, - это мое понимание node.js, и это тоже может быть немного не по плану.

  1. Libev - это цикл событий, который фактически выполняется внутри node.js для выполнения простых операций цикла событий. Он написан изначально для систем * nix. Libev предоставляет простой, но оптимизированный цикл обработки событий для запуска процесса. Подробнее о libev можно прочитать здесь .

  2. LibEio - это библиотека для асинхронного выполнения ввода-вывода. Он обрабатывает файловые дескрипторы, обработчики данных, сокеты и т. Д. Подробнее об этом можно прочитать здесь .

  3. LibUv - это уровень абстракции поверх libeio, libev, c-ares (для DNS) и iocp (для asynchronous-io Windows). LibUv выполняет, поддерживает и управляет всеми IO и событиями в пуле событий. (в случае с libeio threadpool). Вам следует ознакомиться с руководством Райана Даля по libUv. Это станет для вас более понятным в том, как работает libUv, и тогда вы поймете, как node.js работает поверх libuv и v8.

Чтобы понять только цикл событий javascript, вам следует подумать о просмотре этих видео.

Чтобы увидеть, как libeio используется с node.js для создания асинхронных модулей, вы должны увидеть этот пример .

По сути, внутри node.js происходит то, что цикл v8 запускается и обрабатывает все части javascript, а также модули C ++ [когда они выполняются в основном потоке (согласно официальной документации, node.js сам является однопоточным)]. Находясь за пределами основного потока, libev и libeio обрабатывают его в пуле потоков, а libev обеспечивает взаимодействие с основным циклом. Итак, насколько я понимаю, node.js имеет 1 постоянный цикл событий: это цикл событий v8. Для обработки асинхронных задач C ++ используется пул потоков [через libeio и libev].

Например:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

Который появляется во всех модулях, обычно вызывает функцию Taskв пуле потоков. По завершении он вызывает AfterTaskфункцию в основном потоке. В то время как Eio_REQUESTэто обработчик запроса, который может быть структурой / объектом, целью которого является обеспечение связи между пулом потоков и основным потоком.

Шрек
источник
Полагаться на то, что libuv внутренне использует libev, - хороший способ сделать ваш код не кросс-платформенным. Вам следует заботиться только об общедоступном интерфейсе libuv.
Raynos
1
@Raynos libuv стремится к тому, чтобы в его x-платформе было несколько библиотек. Правильно ? следовательно, использование libuv - хорошая идея
ShrekOverflow
1
@Abhishek From Doc process.nextTick- в следующем цикле цикла событий вызовите этот обратный вызов. Это не простой псевдоним для setTimeout (fn, 0), он намного эффективнее. К какому циклу событий относится это? Цикл событий V8?
Тамил
2
Обратите внимание, что libuv больше не реализуется поверх libev .
strcat 05
4
Есть ли способ «увидеть» эту очередь событий? Я хотел бы иметь возможность видеть порядок вызовов в стеке и видеть, как туда помещаются новые функции, чтобы лучше понять, что происходит ... есть ли какая-то переменная, которая сообщает вам, что было помещено в очередь событий?
tbarbe 08
20

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

Позвольте мне попытаться объяснить работу модели, управляемой событиями, на абстрактном примере в абстрактной среде UNIX, в контексте Node, на сегодняшний день.

Перспектива программы:

  • Механизм сценария начинает выполнение сценария.
  • Каждый раз, когда встречается операция, связанная с ЦП, она полностью выполняется в режиме реального времени (на реальной машине).
  • Каждый раз, когда встречается операция, связанная с вводом-выводом, запрос и его обработчик завершения регистрируются с помощью «механизма обработки событий» (виртуальной машины).
  • Повторяйте операции таким же образом, пока сценарий не закончится. Операция с привязкой к ЦП - выполнение операций с привязкой к вводу-выводу, запрос к оборудованию, как указано выше.
  • Когда ввод-вывод завершается, слушатели вызываются обратно.

Механизм обработки событий, описанный выше, называется структурой цикла событий libuv AKA. Node использует эту библиотеку для реализации своей модели программирования, управляемой событиями.

Перспектива узла:

  • Имейте один поток для размещения среды выполнения.
  • Подберите пользовательский скрипт.
  • Скомпилируйте его в родной [leverage v8]
  • Загрузите двоичный файл и перейдите в точку входа.
  • Скомпилированный код выполняет действия, связанные с процессором, в режиме реального времени, используя примитивы программирования.
  • Многие коды ввода-вывода и таймера имеют собственные оболочки. Например, сетевой ввод-вывод.
  • Таким образом, вызовы ввода-вывода направляются от сценария к мостам C ++, причем дескриптор ввода-вывода и обработчик завершения передаются в качестве аргументов.
  • Собственный код выполняет цикл libuv. Он получает цикл, ставит в очередь низкоуровневое событие, которое представляет ввод-вывод, и встроенную оболочку обратного вызова в структуру цикла libuv.
  • Собственный код возвращается к сценарию - в данный момент операции ввода-вывода не выполняются!
  • Пункты, указанные выше, повторяются много раз, пока не будет выполнен весь код, не связанный с вводом-выводом, и пока весь код ввода-вывода не будет зарегистрирован в libuv.
  • Наконец, когда в системе нечего выполнять, узел передает управление libuv
  • libuv начинает действовать, он собирает все зарегистрированные события, запрашивает операционную систему, чтобы проверить их работоспособность.
  • Те, которые готовы к вводу-выводу в неблокирующем режиме, подбираются, ввод-вывод выполняется и выполняются обратные вызовы. Один за другим.
  • Те, которые еще не готовы (например, чтение сокета, для которого другая конечная точка еще ничего не записала), будут продолжать проверяться с ОС, пока они не станут доступными.
  • Цикл внутренне поддерживает постоянно увеличивающийся таймер. Когда приложение запрашивает отложенный обратный вызов (например, setTimeout), это значение внутреннего таймера используется для вычисления правильного времени для запуска обратного вызова.

Хотя большинство функций обслуживается таким образом, некоторые (асинхронные версии) файловые операции выполняются с помощью дополнительных потоков, хорошо интегрированных в libuv. В то время как операции сетевого ввода-вывода могут ожидать внешнего события, такого как другая конечная точка, отправляющая данные и т. Д., Файловые операции требуют некоторой работы от самого узла. Например, если вы открываете файл и ждете, пока fd будет готов с данными, этого не произойдет, поскольку на самом деле никто не читает! В то же время, если вы читаете из файла, встроенного в основной поток, это может потенциально блокировать другие действия в программе и может сделать видимыми проблемы, поскольку файловые операции очень медленные по сравнению с действиями, связанными с процессором. Таким образом, внутренние рабочие потоки (настраиваемые с помощью переменной среды UV_THREADPOOL_SIZE) используются для работы с файлами,

Надеюсь это поможет.

Гириш Пунатил
источник
Как вы узнали об этом, можете указать мне источник?
Сурадж Джайн,
18

Введение в libuv

Проект node.js начался в 2009 году как среда JavaScript, отделенная от браузера. Используя V8 от Google и libev Марка Леманна , node.js объединил модель ввода-вывода - намеченную - с языком, который хорошо подходил к стилю программирования; из-за того, как он был сформирован браузерами. По мере роста популярности node.js важно было заставить его работать в Windows, но libev работала только в Unix. Эквивалент Windows механизмов уведомления о событиях ядра, таких как kqueue или (e) poll, - это IOCP. libuv была абстракцией от libev или IOCP в зависимости от платформы, предоставляя пользователям API на основе libev. В node-v0.9.0 версия libuv libev была удалена .

Также одна картинка, описывающая цикл событий в Node.js от @ BusyRich


Обновление 09.05.2017

В этом документе цикл событий Node.js ,

На следующей диаграмме показан упрощенный обзор порядка операций цикла событий.

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

примечание: каждое окно будет называться «фазой» цикла событий.

Обзор фаз

  • таймеры : эта фаза выполняет обратные вызовы, запланированные setTimeout()и setInterval().
  • Обратные вызовы ввода-вывода : выполняет почти все обратные вызовы, за исключением закрытых обратных вызовов , запланированных таймерами и setImmediate().
  • холостой ход, подготовка : используется только для внутреннего использования.
  • опрос : получение новых событий ввода / вывода; узел будет заблокирован здесь, когда это необходимо.
  • check : setImmediate()здесь вызываются обратные вызовы.
  • закрыть обратные вызовы : например socket.on('close', ...).

Между каждым запуском цикла событий Node.js проверяет, ожидает ли он каких-либо асинхронных операций ввода-вывода или таймеров, и аккуратно завершает работу, если их нет.

Zangw
источник
Вы процитировали это " In the node-v0.9.0 version of libuv libev was removed", но в nodejs об этом нет описания changelog. github.com/nodejs/node/blob/master/CHANGELOG.md . И если libev будет удалена, то как теперь выполняется асинхронный ввод-вывод в nodejs?
intekhab 07
@intekhab, по этой ссылке , я думаю, что libuv на основе libeio можно использовать как цикл событий в node.js.
zangw 08
@intekhab, я думаю, libuv реализует все функции, связанные с вводом-выводом и опросом. здесь проверьте этот документ: docs.libuv.org/en/v1.x/loop.html
mohit kaushik
13

В архитектуре NodeJs есть один цикл событий.

Модель цикла событий Node.js

Узловые приложения работают в однопоточной модели, управляемой событиями. Однако Node реализует пул потоков в фоновом режиме, чтобы можно было выполнять работу.

Node.js добавляет работу в очередь событий, а затем запускает единственный поток, выполняющий цикл событий. Цикл событий захватывает верхний элемент в очереди событий, выполняет его, а затем захватывает следующий элемент.

При выполнении кода, который дольше живет или имеет блокирующий ввод-вывод, вместо прямого вызова функции он добавляет функцию в очередь событий вместе с обратным вызовом, который будет выполнен после завершения функции. Когда все события в очереди событий Node.js были выполнены, приложение Node.js завершает работу.

Цикл событий начинает вызывать проблемы, когда функции нашего приложения блокируются при вводе-выводе.

Node.js использует обратные вызовы событий, чтобы не ждать блокировки ввода-вывода. Следовательно, любые запросы, которые выполняют блокирующий ввод-вывод, выполняются в другом потоке в фоновом режиме.

Когда событие, которое блокирует ввод-вывод, извлекается из очереди событий, Node.js извлекает поток из пула потоков и выполняет функцию там, а не в основном потоке цикла событий. Это предотвращает задержку блокирующим вводом-выводом остальных событий в очереди событий.

Питер Хауге
источник
8

В libuv есть только один цикл событий, V8 - это просто механизм выполнения JS.

Уоррен Чжоу
источник
1

Как новичок в javascript, я тоже сомневался, содержит ли NodeJS 2 цикла событий? После долгого исследования и обсуждения с одним из участников V8 я получил следующие концепции.

  • Цикл событий - это фундаментальная абстрактная концепция модели программирования JavaScript. Таким образом, движок V8 предоставляет реализацию по умолчанию для цикла событий, которую средства внедрения (браузер, узел) могут заменять или расширять . Вы, ребята, можете найти реализацию цикла событий V8 по умолчанию здесь
  • В NodeJS существует только один цикл событий , который обеспечивается средой выполнения узла. Реализация цикла событий по умолчанию V8 была заменена реализацией цикла событий NodeJS.
arunjos007
источник
0

pbkdf2Функция имеет реализацию JavaScript , но он на самом деле делегирует все работы предстоит сделать на стороне C ++.

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>);

ресурс: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

У модуля Libuv есть еще одна ответственность, которая актуальна для некоторых очень специфических функций в стандартной библиотеке.

Для некоторых вызовов стандартных библиотечных функций сторона Node C ++ и Libuv решают полностью выполнять дорогостоящие вычисления вне цикла обработки событий.

Вместо этого они используют нечто, называемое пулом потоков, пул потоков представляет собой серию из четырех потоков, которые можно использовать для выполнения дорогостоящих в вычислительном отношении задач, таких как pbkdf2функция.

По умолчанию Libuv создает 4 потока в этом пуле потоков.

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

Многие функции, включенные в стандартную библиотеку Node, автоматически используют этот пул потоков. Вpbkdf2Функция является одной из них.

Наличие этого пула потоков очень важно.

Таким образом, Node не является действительно однопоточным, потому что есть другие потоки, которые Node использует для выполнения некоторых дорогостоящих в вычислительном отношении задач.

Если бы пул событий отвечал за выполнение дорогостоящей в вычислительном отношении задачи, то наше приложение Node не могло бы делать ничего другого.

Наш ЦП выполняет все инструкции внутри потока одну за другой.

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

Даниэль
источник