Explorando los aspectos internos de Node.js

 

 

 

  • Anuncie en la revista Smashing
  • SmashingConf Friburgo 2024

  • Índice
    1. Requisitos previos
  • ¿Qué es Node.js?
  • Estructura interna de Node.js
  • API interesantes para programas del lado del servidor
  • Experimento: cómo fs.writeFilese crea un archivo nuevo
    1. ¿Qué ganamos con esta comprensión?
  • ¿Node.js es de un solo subproceso?
  • Se delegan tareas de bajo nivel
  • ¿Qué sucede con nuestro programa de creación de archivos?
  • Conclusión
  • Node.js es una herramienta interesante para desarrolladores web. Con su alto nivel de concurrencia, se ha convertido en un candidato líder para las personas que eligen herramientas para usar en el desarrollo web. En este artículo, aprenderemos qué constituye Node.js, le daremos una definición significativa, comprenderemos cómo interactúan las partes internas de Node.js entre sí y exploraremos el repositorio de proyectos de Node.js en GitHub.

     

    Desde la presentación de Node.js por Ryan Dahl en la JSConf europea el 8 de noviembre de 2009, ha tenido un amplio uso en toda la industria tecnológica. Empresas como Netflix, Uber y LinkedIn dan credibilidad a la afirmación de que Node.js puede soportar una gran cantidad de tráfico y concurrencia.

    Armados con conocimientos básicos, los desarrolladores principiantes e intermedios de Node.js luchan con muchas cosas: "¡Es sólo un tiempo de ejecución!" "¡Tiene bucles de eventos!" "¡Node.js es de un solo subproceso como JavaScript!"

    Si bien algunas de estas afirmaciones son ciertas, profundizaremos en el tiempo de ejecución de Node.js, comprenderemos cómo ejecuta JavaScript, veremos si en realidad es de un solo subproceso y, finalmente, comprenderemos mejor la interconexión entre sus dependencias principales, V8 y libuv. .

    Requisitos previos

    • Conocimientos básicos de JavaScript.
    • Familiaridad con la semántica de Node.js ( require, fs)

    ¿Qué es Node.js?

    Puede resultar tentador asumir lo que mucha gente ha creído sobre Node.js, siendo la definición más común que es un tiempo de ejecución para el lenguaje JavaScript . Para considerar esto, debemos entender qué llevó a esta conclusión.

     

    Node.js se describe a menudo como una combinación de C++ y JavaScript. La parte de C++ consta de enlaces que ejecutan código de bajo nivel que permiten acceder al hardware conectado a la computadora. La parte de JavaScript toma JavaScript como código fuente y lo ejecuta en un intérprete popular del lenguaje, llamado motor V8 .

    Con este entendimiento, podríamos describir Node.js como una herramienta única que combina JavaScript y C++ para ejecutar programas fuera del entorno del navegador.

    Pero, ¿podríamos realmente llamarlo tiempo de ejecución? Para determinar eso, definamos qué es un tiempo de ejecución.

    ¿Qué es un tiempo de ejecución? https://t.co/eaF4CoWecX

    – Christian Nwamba (@codebeast) 5 de marzo de 2020

    En una de sus respuestas en StackOverflow, DJNA define un entorno de ejecución como "todo lo que necesita para ejecutar un programa, pero ninguna herramienta para cambiarlo". Según esta definición, podemos decir con confianza que todo lo que sucede mientras ejecutamos nuestro código (en cualquier idioma) se ejecuta en un entorno de ejecución.

    Otros idiomas tienen su propio entorno de ejecución. Para Java, es Java Runtime Environment (JRE). Para .NET, es Common Language Runtime (CLR). Para Erlang, es BEAM.

    Sin embargo, algunos de estos runtimes tienen otros lenguajes que dependen de ellos. Por ejemplo, Java tiene Kotlin, un lenguaje de programación que compila un código que un JRE puede entender. Erlang tiene elixir. Y sabemos que existen muchas variantes para el desarrollo de .NET, todas las cuales se ejecutan en CLR, conocido como .NET Framework.

    Ahora entendemos que un tiempo de ejecución es un entorno proporcionado para que un programa pueda ejecutarse exitosamente, y sabemos que V8 y una gran cantidad de bibliotecas C++ hacen posible que se ejecute una aplicación Node.js. Node.js en sí es el tiempo de ejecución real que une todo para hacer de esas bibliotecas una entidad, y entiende un solo lenguaje, JavaScript, independientemente de con qué esté construido Node.js.

    Estructura interna de Node.js

    Cuando intentamos ejecutar un programa Node.js (como index.js) desde nuestra línea de comando usando el comando node index.js, estamos llamando al tiempo de ejecución de Node.js. Este tiempo de ejecución, como se mencionó, consta de dos dependencias independientes, V8 y libuv.

    Dependencias principales de Node.js ( vista previa grande )

    V8 es un proyecto creado y mantenido por Google. Toma el código fuente de JavaScript y lo ejecuta fuera del entorno del navegador. Cuando ejecutamos un programa a través de un nodecomando, el tiempo de ejecución de Node.js pasa el código fuente a V8 para su ejecución.

     

    La biblioteca libuv contiene código C++ que permite el acceso de bajo nivel al sistema operativo. Funciones como redes, escritura en el sistema de archivos y concurrencia no se incluyen de forma predeterminada en V8, que es la parte de Node.js que ejecuta nuestro código JavaScript. Con su conjunto de bibliotecas, libuv proporciona estas utilidades y más en un entorno Node.js.

    Node.js es el pegamento que mantiene unidas las dos bibliotecas, convirtiéndose así en una solución única. Durante la ejecución de un script, Node.js comprende a qué proyecto pasar el control y cuándo.

    API interesantes para programas del lado del servidor

    Si estudiamos un poco de historia de JavaScript, sabremos que está destinado a agregar alguna funcionalidad e interacción a una página en el navegador. Y en el navegador interactuaríamos con los elementos del modelo de objetos de documento (DOM) que componen la página. Para ello, existe un conjunto de API, denominadas colectivamente API DOM.

    El DOM existe sólo en el navegador; es lo que se analiza para representar una página y básicamente está escrito en el lenguaje de marcado conocido como HTML. Además, el navegador existe en una ventana, de ahí el windowobjeto, que actúa como raíz para todos los objetos de la página en un contexto de JavaScript. Este entorno se denomina entorno de navegador y es un entorno de ejecución para JavaScript.

    Las API de Node.js interactúan con libuv ( vista previa grande )

    En un entorno Node.js, no tenemos nada parecido a una página ni a un navegador; esto anula nuestro conocimiento del objeto de ventana global . Lo que sí tenemos es un conjunto de API que interactúan con el sistema operativo para proporcionar funcionalidad adicional a un programa JavaScript. Estas API para Node.js ( fs, path, buffer, events, HTTP, etc.), tal como las tenemos, existen solo para Node.js y las proporciona Node.js (en sí mismo un tiempo de ejecución) para que podamos ejecutar programas escritos para Nodo.js.

    Experimento: cómo fs.writeFilese crea un archivo nuevo

    Si V8 se creó para ejecutar JavaScript fuera del navegador, y si un entorno Node.js no tiene el mismo contexto o entorno que un navegador, entonces ¿cómo haríamos algo como acceder al sistema de archivos o crear un servidor HTTP?

    Como ejemplo, tomemos una aplicación Node.js simple que escribe un archivo en el sistema de archivos en el directorio actual:

    const fs = require("fs")fs.writeFile("./test.txt", "text");

    Como se muestra, estamos intentando escribir un nuevo archivo en el sistema de archivos. Esta función no está disponible en el lenguaje JavaScript; solo está disponible en un entorno Node.js. ¿Cómo se ejecuta esto?

    Para entender esto, hagamos un recorrido por el código base de Node.js.

    Al dirigirnos al repositorio de GitHub para Node.js , vemos dos carpetas principales srcy lib. La libcarpeta tiene el código JavaScript que proporciona el bonito conjunto de módulos que se incluyen de forma predeterminada con cada instalación de Node.js. La srccarpeta contiene las bibliotecas de C++ para libuv.

     

    Si miramos en la libcarpeta y revisamos el fs.jsarchivo , veremos que está lleno de un impresionante código JavaScript. En la línea 1880 , notaremos una exportsdeclaración. Esta declaración exporta todo a lo que podemos acceder importando el fsmódulo, y podemos ver que exporta una función llamada writeFile.

    Buscar function writeFile((donde está definida la función) nos lleva a la línea 1303 , donde vemos que la función está definida con cuatro parámetros:

    function writeFile(path, data, options, callback) { callback = maybeCallback(callback || options); options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); const flag = options.flag || 'w'; if (!isArrayBufferView(data)) { validateStringAfterArrayBufferView(data, 'data'); data = Buffer.from(data, options.encoding || 'utf8'); } if (isFd(path)) { const isUserFd = true; writeAll(path, isUserFd, data, 0, data.byteLength, callback); return; } fs.open(path, flag, options.mode, (openErr, fd) = { if (openErr) { callback(openErr); } else { const isUserFd = false; writeAll(fd, isUserFd, data, 0, data.byteLength, callback); } });}

    En las líneas 1315 y 1324 , vemos que writeAllse llama a una única función, después de algunas comprobaciones de validación. Esta función la encontramos en la línea 1278 del mismo fs.jsarchivo .

    function writeAll(fd, isUserFd, buffer, offset, length, callback) { // write(fd, buffer, offset, length, position, callback) fs.write(fd, buffer, offset, length, null, (writeErr, written) = { if (writeErr) { if (isUserFd) { callback(writeErr); } else { fs.close(fd, function close() { callback(writeErr); }); } } else if (written === length) { if (isUserFd) { callback(null); } else { fs.close(fd, callback); } } else { offset += written; length -= written; writeAll(fd, isUserFd, buffer, offset, length, callback); } });}

    También es interesante observar que este módulo intenta llamarse a sí mismo. Esto lo vemos en la línea 1280 , donde está llamando fs.write. Buscando la writefunción, descubriremos un poco de información. Health Tips

    La writefunción comienza en la línea 571 y recorre aproximadamente 42 líneas . Vemos un patrón recurrente en esta función: la forma en que llama a una función en el bindingmódulo, como se ve en las líneas 594 y 612 . Una función en el bindingmódulo se llama no solo en esta función, sino en prácticamente cualquier función que se exporte en el fs.jsarchivo . Algo debe tener algo muy especial.

    La bindingvariable se declara en la línea 58 , en la parte superior del archivo, y al hacer clic en esa llamada de función se revela cierta información, con la ayuda de GitHub.

    Declaración de la variable vinculante ( vista previa grande )

    Esta internalBindingfunción se encuentra en el módulo llamado loaders . La función principal del módulo Loaders es cargar todas las bibliotecas libuv y conectarlas a través del proyecto V8 con Node.js. Cómo lo hace es bastante mágico, pero para aprender más podemos observar de cerca la writeBufferfunción que llama el fsmódulo.

     

    Deberíamos mirar dónde se conecta esto con libuv y dónde entra V8. En la parte superior del módulo de cargadores, hay buena documentación que dice esto:

    // This file is compiled and run by node.cc before bootstrap/node.js// was called, therefore the loaders are bootstraped before we start to// actually bootstrap Node.js. It creates the following objects://// C++ binding loaders:// - process.binding(): the legacy C++ binding loader, accessible from user land// because it is an object attached to the global process object.// These C++ bindings are created using NODE_BUILTIN_MODULE_CONTEXT_AWARE()// and have their nm_flags set to NM_F_BUILTIN. We do not make any guarantees// about the stability of these bindings, but still have to take care of// compatibility issues caused by them from time to time.// - process._linkedBinding(): intended to be used by embedders to add// additional C++ bindings in their applications. These C++ bindings// can be created using NODE_MODULE_CONTEXT_AWARE_CPP() with the flag// NM_F_LINKED.// - internalBinding(): the private internal C++ binding loader, inaccessible// from user land unless through `require('internal/test/binding')`.// These C++ bindings are created using NODE_MODULE_CONTEXT_AWARE_INTERNAL()// and have their nm_flags set to NM_F_INTERNAL.//// Internal JavaScript module loader:// - NativeModule: a minimal module system used to load the JavaScript core// modules found in lib/**/*.js and deps/**/*.js. All core modules are// compiled into the node binary via node_javascript.cc generated by js2c.py,// so they can be loaded faster without the cost of I/O. This class makes the// lib/internal/*, deps/internal/* modules and internalBinding() available by// default to core modules, and lets the core modules require itself via// require('internal/bootstrap/loaders') even when this file is not written in// CommonJS style.

    Lo que aprendemos aquí es que para cada módulo llamado desde el bindingobjeto en la sección JavaScript del proyecto Node.js, hay un equivalente en la sección C++, en la srccarpeta.

    En nuestro fsrecorrido, vemos que el módulo que hace esto se encuentra ennode_file.cc . Cada función a la que se puede acceder a través del módulo está definida en el archivo; por ejemplo, tenemos la writeBufferlínea 2258 . La definición real de ese método en el archivo C++ está en la línea 1785 . Además, la llamada a la parte de libuv que realiza la escritura real en el archivo se puede encontrar en las líneas 1809 y 1815 , donde la función libuv uv_fs_writese llama de forma asincrónica.

    ¿Qué ganamos con esta comprensión?

    Al igual que muchos otros tiempos de ejecución de lenguajes interpretados, el tiempo de ejecución de Node.js se puede piratear. Con una mayor comprensión, podríamos hacer cosas que son imposibles con la distribución estándar simplemente mirando el código fuente. Podríamos agregar bibliotecas para realizar cambios en la forma en que se llaman algunas funciones. Pero, sobre todo, esta comprensión es la base para una mayor exploración.

    ¿Node.js es de un solo subproceso?

    Ubicado en libuv y V8, Node.js tiene acceso a algunas funcionalidades adicionales que un motor JavaScript típico que se ejecuta en el navegador no tiene.

     

    Cualquier JavaScript que se ejecute en un navegador se ejecutará en un solo hilo. Un hilo en la ejecución de un programa es como una caja negra ubicada encima de la CPU en la que se ejecuta el programa. En un contexto de Node.js, parte del código podría ejecutarse en tantos subprocesos como nuestras máquinas puedan transportar.

    Para verificar esta afirmación en particular, exploremos un fragmento de código simple.

    const fs = require("fs");// A little benchmarkingconst startTime = Date.now()fs.writeFile("./test.txt", "test", (err) = { If (error) { console.log(err) } console.log("1 Done: ", Date.now() — startTime)});

    En el fragmento anterior, intentamos crear un nuevo archivo en el disco en el directorio actual. Para ver cuánto tiempo podría tardar esto, agregamos un pequeño punto de referencia para monitorear la hora de inicio del script, que nos da la duración en milisegundos del script que está creando el archivo.

    Si ejecutamos el código anterior, obtendremos un resultado como este:

    Tiempo necesario para crear un solo archivo en Node.js ( vista previa grande )

    $ node ./test.js - 1 Done: 0.003s

    Esto es muy impresionante: sólo 0,003 segundos.

    Pero hagamos algo realmente interesante. Primero, dupliquemos el código que genera el nuevo archivo y actualicemos el número en la declaración de registro para reflejar sus posiciones:

    const fs = require("fs");// A little benchmarkingconst startTime = Date.now()fs.writeFile("./test1.txt", "test", function (err) { if (err) { console.log(err) } console.log("1 Done: %ss", (Date.now() — startTime) / 1000)});fs.writeFile("./test2.txt", "test", function (err) { if (err) { console.log(err) } console.log("2 Done: %ss", (Date.now() — startTime) / 1000)});fs.writeFile("./test3.txt", "test", function (err) { if (err) { console.log(err) } console.log("3 Done: %ss", (Date.now() — startTime) / 1000)});fs.writeFile("./test4.txt", "test", function (err) { if (err) { console.log(err) } console.log("4 Done: %ss", (Date.now() — startTime) / 1000)});

    Si intentamos ejecutar este código, obtendremos algo que nos dejará boquiabiertos. Aquí está mi resultado:

    Creando muchos archivos a la vez ( vista previa grande )

    Primero, notaremos que los resultados no son consistentes. En segundo lugar, vemos que el tiempo ha aumentado. ¿Lo que está sucediendo?

    Se delegan tareas de bajo nivel

    Node.js es de un solo subproceso, como sabemos ahora. Partes de Node.js están escritas en JavaScript y otras en C++. Node.js utiliza los mismos conceptos de bucle de eventos y pila de llamadas con los que estamos familiarizados en el entorno del navegador, lo que significa que las partes de JavaScript de Node.js son de un solo subproceso. Pero la tarea de bajo nivel que requiere hablar con un sistema operativo no es de un solo subproceso.

    Delegación de tareas de bajo nivel de Node.js ( vista previa grande )

    Cuando Node.js reconoce que una llamada está destinada a libuv, delega esta tarea a libuv. En su funcionamiento, libuv requiere subprocesos para algunas de sus bibliotecas, de ahí el uso del grupo de subprocesos para ejecutar programas Node.js cuando son necesarios.

     

    De forma predeterminada, el grupo de subprocesos de Node.js proporcionado por libuv tiene cuatro subprocesos. Podríamos aumentar o reducir este grupo de subprocesos llamando process.env.UV_THREADPOOL_SIZEa la parte superior de nuestro script.

    // script.jsprocess.env.UV_THREADPOOL_SIZE = 6;// …// …

    ¿Qué sucede con nuestro programa de creación de archivos?

    Parece que una vez que invocamos el código para crear nuestro archivo, Node.js llega a la parte libuv de su código, que dedica un hilo para esta tarea. Esta sección en libuv obtiene información estadística sobre el disco antes de trabajar en el archivo.

    Esta verificación estadística podría tardar un poco en completarse; por lo tanto, el hilo se libera para otras tareas hasta que se completa la verificación estadística. Cuando se completa la verificación, la sección libuv ocupa cualquier hilo disponible o espera hasta que un hilo esté disponible para ella.

    Sólo tenemos cuatro llamadas y cuatro subprocesos, por lo que hay suficientes subprocesos para todos. La única pregunta es qué tan rápido procesará cada hilo su tarea. Notaremos que el primer código que ingrese al grupo de subprocesos devolverá su resultado primero y bloqueará todos los demás subprocesos mientras ejecuta su código.

    Conclusión

    Ahora entendemos qué es Node.js. Sabemos que es un tiempo de ejecución. Hemos definido qué es un tiempo de ejecución. Y hemos profundizado en lo que constituye el tiempo de ejecución proporcionado por Node.js.

    Hemos recorrido un largo camino. Y desde nuestro pequeño recorrido por el repositorio de Node.js en GitHub , podemos explorar cualquier API que nos pueda interesar, siguiendo el mismo proceso que realizamos aquí. Node.js es de código abierto, por lo que seguramente podemos profundizar en el código fuente, ¿no?

    Aunque hemos tocado varios de los niveles bajos de lo que sucede en el tiempo de ejecución de Node.js, no debemos asumir que lo sabemos todo. Los recursos a continuación apuntan a cierta información sobre la cual podemos desarrollar nuestro conocimiento:

    • Introducción a Node.js
      Al ser un sitio web oficial, Node.dev explica qué es Node.js, así como sus administradores de paquetes, y enumera los marcos web creados sobre él.
    • “ JavaScript Node.js ”, El libro para principiantes de Node
      Este libro de Manuel Kiessling hace un trabajo fantástico al explicar Node.js, después de advertir que JavaScript en el navegador no es lo mismo que el de Node.js, aunque ambos son escrito en el mismo idioma.
    • Comenzando con Node.js
      Este libro para principiantes va más allá de una explicación del tiempo de ejecución. Enseña sobre paquetes y transmisiones y cómo crear un servidor web con el marco Express.
    • LibUV
      Esta es la documentación oficial del código C++ de soporte del tiempo de ejecución de Node.js.
    • V8
      Esta es la documentación oficial del motor JavaScript que permite escribir Node.js con JavaScript.

    (ra, il, al)Explora más en

    • Nodo.js
    • Herramientas
    • javascript





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Explorando los aspectos internos de Node.js

    Explorando los aspectos internos de Node.js

    Anuncie en la revista Smashing SmashingConf Friburgo 2024 Índice Requisitos previos

    programar

    es

    https://aprendeprogramando.es/static/images/programar-explorando-los-aspectos-internos-de-node-1022-0.jpg

    2024-05-21

     

    Explorando los aspectos internos de Node.js
    Explorando los aspectos internos de Node.js

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20