Flujo en nodo

1. El concepto de
flujo Un flujo es una colección de datos, similar a matrices y cadenas. Pero la transmisión no accede a todos los datos a la vez, sino que parte de ellos se envía / recibe (tipo chunk), por lo que no necesita ocupar un bloque de memoria tan grande, especialmente adecuado para escenarios que tratan con una gran cantidad de datos (externos)

Stream tiene características de canalización, por ejemplo:


const grep = ... // A stream for the grep output
const wc = ... // A stream for the wc input
grep.pipe(wc)

Muchos módulos nativos están basados ​​en flujos, incluido el proceso stdin / stdout / stderr:

Flujo en nodo

Por ejemplo, escenarios comunes:


const fs = require('fs');
const server = require('http').createServer();

server.on('request', (req, res) => {
  const src = fs.createReadStream('./big.file');
  src.pipe(res);
});

server.listen(8000);

El método de tubería toma la salida del flujo legible (fuente de datos) como la entrada (destino) del flujo grabable y conecta directamente el flujo de salida del archivo leído como entrada al flujo de salida de la respuesta HTTP, evitando así leer el archivo completo en la memoria.

PD incluso la implementación interna de console.log () que se usa a diario también se transmite

2. Tipos de transmisiones
Hay 4 transmisiones básicas en Node:

Legible

La secuencia legible es una abstracción de la fuente, desde la cual se pueden consumir datos, como fs.createReadStream

Escribible

Una secuencia de escritura es una abstracción de un destino que puede escribir datos, como fs.createWriteStream

Dúplex

La transmisión dúplex es legible y de escritura, como el socket TCP

Transformar

El flujo de conversión es esencialmente un flujo dúplex, que se utiliza para modificar o convertir datos al escribir y leer datos, como zlib.createGzip comprimiendo datos con gzip

El flujo de conversión se puede considerar como una función del flujo de escritura de entrada y el flujo de salida legible

PS tiene un flujo de conversión llamado (Pass) Through Stream, que es similar a identity = x => x en FP

3. La canalización
src.pipe (res) requiere que la fuente debe ser legible y el destino debe poder escribir. Por lo tanto, si la secuencia dúplex es canalizada, se puede llamar en una cadena como una canalización Linux:

readableSrc
  .pipe(transformStream1)
  .pipe(transformStream2)
  .pipe(finalWrtitableDest)

El método pipe () devuelve el flujo de destino, por lo que:


// a (readable), b and c (duplex), and d (writable)
a.pipe(b).pipe(c).pipe(d)
// 等价于
a.pipe(b)
b.pipe(c)
c.pipe(d)
# Linux下,等价于
$ a | b | c | d

4. La transmisión y el evento
impulsado por eventos es una característica importante del diseño de Node. Muchos objetos nativos de Node se implementan según el mecanismo de eventos (módulo EventEmitter), incluido el flujo (módulo de transmisión):


Most of Node’s objects — like HTTP requests, responses, and streams — implement the EventEmitter module so they can provide a way to emit and listen to events.

Todas las transmisiones son instancias de EventEmitter y leen y escriben datos a través del mecanismo de eventos. Por ejemplo, el método pipe () mencionado anteriormente es equivalente a:


// readable.pipe(writable)

readable.on('data', (chunk) => {
  writable.write(chunk);
});
readable.on('end', () => {
  writable.end();
});

PSpipe también maneja algunas otras cosas, como el manejo de errores, EoF y la situación en la que una determinada secuencia es más rápida / más lenta

Los principales eventos y métodos de flujo de lectura y escritura son los siguientes:

Flujo en nodo

Los principales eventos de Readable son:

evento de datos: se activa cuando la transmisión entrega un fragmento al usuario

evento final: se activa cuando no hay más datos para consumir de la transmisión

Los principales eventos de Writable son:

El evento de drenaje, el flujo se corta, esta es una señal de que el flujo de escritura puede recibir más datos

Finalizar evento, que se activa cuando todos los datos se han descargado al sistema inferior.

Cinco. Dos modos de
flujo legible: en pausa y en flujo . Un flujo legible es en flujo o en pausa, también conocido como tirar y empujar.

Después de la creación, se encuentra en estado de pausa de forma predeterminada y los datos se pueden leer mediante el método read (). Si estás en el estado Flujo, los datos continuarán fluyendo. En este momento, solo necesitas escuchar el evento para usar los datos. Si no hay ningún usuario, los datos se perderán, por lo que escucharás el evento de datos de la transmisión legible. De hecho, monitorear el evento de datos La transmisión legible se cambia de Pausada a Fluida, y el oyente de eventos de datos se elimina y se vuelve a cambiar. Si necesita cambiar manualmente, puede hacerlo a través de resume () y pause ()

No se preocupe por estos cuando use pipe (), se manejará automáticamente:

Readable activa el evento de datos hasta que Writable esté demasiado ocupado

Llamadas de tubería Readable.pause () después de recibir la señal para ingresar al modo de pausa

Cuando Writable no funciona durante un tiempo, se activará el evento de drenaje. En este momento, la tubería llama a Readable.resume () para ingresar al modo de flujo, y Readable activará el evento de datos.

HighWaterMark y backpressure se
utilizan en realidad para tratar el fenómeno de Backpressure. En pocas palabras, Backpressure es la velocidad de consumo aguas abajo que limita la transmisión, lo que provoca la presión inversa de aguas abajo a aguas arriba.

Si la tasa de consumo es más lenta que la tasa de producción, la acumulación se producirá en sentido descendente y los datos que sean demasiado tardíos para procesar se almacenarán en el búfer de escritura. Si no se procesan (límite actual), el búfer seguirá creciendo, lo que puede desbordarse y provocar errores o pérdida de datos.

El signo del fenómeno de contrapresión es que Writable.write () devuelve falso, lo que indica que la cantidad de datos que se procesarán desde aguas arriba ha alcanzado la marca de agua alta (marca de agua alta, por defecto 16 kb):


Buffer level when stream.write() starts returning false. Defaults to 16384 (16kb), or 16 for objectMode streams.

Esta es una señal de que el río abajo se está poniendo un poco nervioso (todo ha estado lo suficientemente ocupado durante un tiempo). Se recomienda limitar la corriente ascendente en este momento, es decir, llamar a Readable.pause () para detenerse primero y darle más tiempo al flujo descendente para procesar los datos acumulados. Si el flujo descendente se siente relajado, activará el evento darin, lo que indica que es capaz de procesar más datos en este momento. , Por lo que la puerta debe abrirse en este momento (Readable.resume ())

Tenga en cuenta que los datos legibles se almacenarán en la memoria caché hasta que haya una capacidad de escritura para consumir estos datos. Entonces, el estado de pausa solo significa que no fluye hacia abajo y los datos almacenados en caché todavía están en el búfer legible. Por lo tanto, si el flujo no es limitado, los datos que son demasiado tarde para procesarse se almacenarán en caché en sentido descendente y continuarán acumulándose. Si el flujo es limitado, esta parte de los datos se almacenará en caché en sentido ascendente y no continuará acumulándose debido al límite actual.

Además, Readable también tiene el concepto de highWaterMark:


The maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource. Defaults to 16384 (16kb), or 16 for objectMode streams

Es una restricción en la velocidad de lectura de la fuente de datos real (como la lectura de archivos del disco) para evitar la acumulación de caché debido a una velocidad de producción demasiado rápida (como un empujón violento ()). Por lo tanto, el método de trabajo normal de Flowing Readable es empujar () - empujar () - empujar () ... Oye, encontré que la cantidad en el búfer ha sido suficiente para un trozo, y lo escupo hacia abajo. De manera similar, la bandera que Readable toca highWaterMark es que push () devuelve falso, lo que indica que el búfer de Readable no está tan vacío. En este momento, si push () continúa, sí, también aparecerá BackPressure (La capacidad de consumo de Readable limita la fuente de datos a Velocidad de transmisión legible):


  快-------------慢
数据源-------->Readable------->Writable
                 快--------------慢

Siempre que el flujo ascendente (producción) sea rápido y el flujo descendente (consumo) sea lento, aparecerá BackPressure. Entonces, en el escenario simple de legible.pipe (escribible), los dos párrafos anteriores de BackPressure pueden aparecer

6. Ejemplos de archivos grandes comunes para
flujo de escritura
:


const fs = require('fs');
const file = fs.createWriteStream('./big.file');

for(let i=0; i<= 1e6; i++) {
  file.write('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n');
}

file.end();

Cree una secuencia de escritura que apunte al archivo a través de fs.createWriteStream (), complete los datos a través de write () y end () después de escribir

O de manera más general, directamente nuevo y escribible:


const { Writable } = require('stream');
const outStream = new Writable({
  write(chunk, encoding, callback) {
    console.log(chunk.toString());
    // nowrap version
    // process.stdout.write(chunk.toString());
    callback();
  }
});

process.stdin.pipe(outStream);

Una de las implementaciones de eco más simples es conectar la entrada estándar del proceso actual al flujo de salida personalizado outStream, como un middleware de registro (la entrada estándar fluye a través de outStream, ¿por qué debería ir a la devolución de llamada):


cc
oo
nn
ss
oo
ll
ee

Console {
  log: [Function: bound consoleCall],
  ...
}

Entre los tres parámetros del método write (), el fragmento es un búfer. La codificación es necesaria en algunos escenarios y se puede ignorar la mayor parte del tiempo. La devolución de llamada es una función de notificación que se debe llamar después de que se procesa el fragmento, lo que indica si la escritura es exitosa o no (si falla) , Pase el objeto Error en), similar a next () en el mecanismo de activación de cola

O una implementación de eco más simple:


process.stdin.pipe(process.stdout);

Conecte el flujo de entrada estándar directamente al flujo de salida estándar


Readable stream
const { Readable } = require('stream'); 
const inStream = new Readable();
inStream.push('ABCDEFGHIJKLM');
inStream.push('NOPQRSTUVWXYZ');
inStream.push(null); // No more data
inStream.pipe(process.stdout);

Llene la secuencia legible con datos mediante push, y push (nulo) significa el final. En el ejemplo anterior, todos los datos se leen y luego se entregan a la salida estándar. De hecho, hay una forma más eficiente (enviar los datos al usuario a pedido):


const { Readable } = require('stream'); 
const inStream = new Readable({
  read(size) {
    this.push(String.fromCharCode(this.currentCharCode++));
    if (this.currentCharCode > 90) {
      this.push(null);
    }
  }
});
inStream.currentCharCode = 65;
inStream.pipe(process.stdout);

El método read () escupe un carácter cada vez, cuando el usuario obtiene datos del flujo legible, read () continuará activando el

flujo Duplex / Transform

El flujo dúplex tiene las características de legible y de escritura: se puede utilizar como fuente de datos (productor) o como destino (consumidor). P.ej:


const { Duplex } = require('stream');

const inoutStream = new Duplex({
  write(chunk, encoding, callback) {
    console.log(chunk.toString());
    callback();
  },

  read(size) {
    this.push(String.fromCharCode(this.currentCharCode++));
    if (this.currentCharCode > 90) {
      this.push(null);
    }
  }
});

inoutStream.currentCharCode = 65;
process.stdin.pipe(inoutStream).pipe(process.stdout);

El ejemplo anterior combina los dos ejemplos anteriores. InoutStream está conectado al flujo de salida estándar. AZ se utilizará como fuente de datos para pasar a la salida estándar (impresión). Al mismo tiempo, el flujo de entrada estándar está conectado a inoutStream. Todos los datos de la entrada estándar serán Cerrar sesión, el efecto es el siguiente:


ABCDEFGHIJKLMNOPQRSTUVWXYZcc
oo
nn
ss
oo
ll
ee

Console {
  log: [Function: bound consoleCall],
  ...
}

PS genera primero AZ porque pipe () cambiará la secuencia legible al modo Flujo, por lo que AZ se "transmite" desde el principio

Tenga en cuenta que las partes de lectura y escritura de la secuencia Duplex son completamente independientes, y la lectura y la escritura no se afectan entre sí. Duplex simplemente combina las dos características en un solo objeto, que es como una tubería unidireccional unida como dos palillos.

Transform stream es un flujo dúplex interesante: su salida se calcula en función de la entrada. Entonces, en lugar de implementar los métodos read / write () por separado, solo un método transform () es suficiente:


const { Transform } = require('stream');

const upperCaseTr = new Transform({
  // 函数签名与write一致
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  }
});

process.stdin.pipe(upperCaseTr).pipe(process.stdout);

De manera similar, las partes de lectura y escritura del flujo de transformación también son independientes (no se pasará automáticamente a la parte de lectura sin empuje manual), pero se combinan en forma.

PD Además, además de pasar Buffer / String entre streams, también puedes pasar Object (incluyendo Array). Consulta el Modo de Objeto de Streams para más detalles.

Node proporciona algunos flujos de transformación nativos, como zlib y crypto stream:


const fs = require('fs');
const zlib = require('zlib');
const file = process.argv[2];

fs.createReadStream(file)
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream(file + '.gz'));

Herramienta de línea de comandos simple, compresión gzip. Para obtener más ejemplos, consulte las secuencias de transformación integradas de Node


Flujos de referencia de Node.js: todo lo que necesita saber

Node.js writable.write devuelve falso?

Explore el evento de drenaje en Node.js

Comprenda en profundidad el mecanismo interno de Node.js Stream

Contrapresión en arroyos

Supongo que te gusta

Origin blog.51cto.com/15080030/2592714
Recomendado
Clasificación