Node.js Guides(Module related guides)

这里接上一篇《Node.js Guides(Node.js core concepts)》,继续介绍第三部分,Node.js的模块,原始地址点这里

  • 1、http简单处理

    • 一个server

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      const http = require('http');

      http.createServer((request, response) => {
      const { headers, method, url } = request;
      let body = [];
      request.on('error', (err) => {
      console.error(err);
      }).on('data', (chunk) => {
      body.push(chunk);
      }).on('end', () => {
      body = Buffer.concat(body).toString();

      response.on('error', (err) => {
      console.error(err);
      });

      response.statusCode = 200;
      response.setHeader('Content-Type', 'application/json');
      // response.writeHead(200, {'Content-Type': 'application/json'})

      const responseBody = { headers, method, url, body };

      response.write(JSON.stringify(responseBody));
      response.end();
      // response.end(JSON.stringify(responseBody))

      });
      }).listen(8080);
    • 一个router

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      const http = require('http');

      http.createServer((request, response) => {
      request.on('error', (err) => {
      console.error(err);
      response.statusCode = 400;
      response.end('PONG');
      });
      response.on('error', (err) => {
      console.error(err);
      });
      if (request.method === 'POST' && request.url === '/PING') {
      request.pipe(response);
      } else {
      response.statusCode = 404;
      response.end();
      }
      }).listen(8080);
  • 2、文件系统

    • 不同的操作系统具有不同的文件规则,不要假设用户在怎样的系统中,尽可能使用确定的东西
    • 关于文件的命名:不要使用最低公分母的方式,也就是说命名规则要尽可能的避免出现重名的情况
    • 需要在不同平台运行的方法要尽可能的保证方法的兼容性,适配各种情况
    • 某些文件系统会将返回的文件名转换为大写,这个要注意
    • 在一些系统中对于毫秒是没有分辨的,会将毫秒数截成0,这里要注意
  • 3、流的Backpressuring

    • 背压(Backpressure)机制
    • 背压:交换机在阻止外来数据包发送到堵塞端口的时候可能会发生丢包,而背压就是考验交换机在这个时候避免丢包的能力。为了保证读写双方数据速度的同步性。
    • 在通过背压进行数据处理的时候发生了一些问题,导致数据传输期间缓冲区中的数据累积,当传输的接收端具有复杂的操作,或者由于某种原因而变慢时,会有输入源的数据累积趋势,例如阻塞。要解决这个问题,必须有一个委托系统来确保数据从一个源到另一个源的平滑流动,Node采用了流的方式。这部分的目的是详细说明背压是什么,下面有一些最佳实践,可以确保在实现流的代码是安全和有效的。
    • 在计算机系统中通过pipes、sockets、signals进行进程间的数据传输,在Node中使用了stream机制,内部代码中几乎所有模块都使用了它,作为开发,鼓励使用stream机制。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      const gzip = require('zlib').createGzip();
      const fs = require('fs');
      const inp = fs.createReadStream('The.Matrix.1080p.mkv');
      const out = fs.createWriteStream('The.Matrix.1080p.mkv.gz');
      inp.pipe(gzip).pipe(out);

      const { pipeline } = require('stream');
      const fs = require('fs');
      const zlib = require('zlib');
      pipeline(
      fs.createReadStream('The.Matrix.1080p.mkv'),
      zlib.createGzip(),
      fs.createWriteStream('The.Matrix.1080p.mkv.gz'),
      (err) => {
      if (err) {
      console.error('Pipeline failed', err);
      } else {
      console.log('Pipeline succeeded');
      }
      }
      );

      const stream = require('stream');
      const fs = require('fs');
      const zlib = require('zlib');
      const pipeline = util.promisify(stream.pipeline);
      async function run() {
      try {
      await pipeline(
      fs.createReadStream('The.Matrix.1080p.mkv'),
      zlib.createGzip(),
      fs.createWriteStream('The.Matrix.1080p.mkv.gz'),
      );
      console.log('Pipeline succeeded');
      } catch (err) {
      console.error('Pipeline failed', err);
      }
      }
    • 在某些情况下,可读流可能会过快的为可写流提供数据,远远超过消费者可以处理的数据量,发生这种情况之后消费者会把数据排在内存中以供后续使用,当我们压缩文件将其写入磁盘时,因为写入速度比读取速度慢的多,发生背压,如果没有背压系统,该过程将耗尽系统的内存,也就是说当我们压缩一个巨大的文件时,以很快的速度读取文件,但是压缩之后写入到磁盘中的速度很慢,就会导致文件在内存中大量积压,这就是背压机制所要处理的问题。没有背压这将会导致三个问题:

      • 减慢当前进程中其他部分的执行
      • 一个非常忙碌的垃圾收集器
      • 内存损耗
    • 在Node中源是可读流,而消费者是可写流,当缓冲区超过最大限制或者当前写入正忙时,可写流会返回false,此时背压系统启动,他将暂停可读流发送任何数据并等待消费者再次准备就绪,或者当缓冲区清空之后恢复可读流的数据传输。这样pipe就能在给定的时间内使用定量的内存,垃圾回收器只需要处理内存中的一块儿区域即可,Node缓冲区默认大小设置为16K,可见背压机制是一个非常快速的过程。
    • 为了更好的理解背压,下面是读写流的生命周期流程图,其实就是中间的核心圈加外圈的背压暂停和开始

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
                                                          +===================+
      x--> Piping functions +--> src.pipe(dest) |
      x are set up during |===================|
      x the .pipe method. | Event callbacks |
      +===============+ x |-------------------|
      | Your Data | x They exist outside | .on('close', cb) |
      +=======+=======+ x the data flow, but | .on('data', cb) |
      | x importantly attach | .on('drain', cb) |
      | x events, and their | .on('unpipe', cb) |
      +---------v---------+ x respective callbacks. | .on('error', cb) |
      | Readable Stream +----+ | .on('finish', cb) |
      +-^-------^-------^-+ | | .on('end', cb) |
      ^ | ^ | +-------------------+
      | | | |
      | ^ | |
      ^ ^ ^ | +-------------------+ +=================+
      ^ | ^ +----> Writable Stream +---------> .write(chunk) |
      | | | +-------------------+ +=======+=========+
      | | | |
      | ^ | +------------------v---------+
      ^ | +-> if (!chunk) | Is this chunk too big? |
      ^ | | emit .end(); | Is the queue busy? |
      | | +-> else +-------+----------------+---+
      | ^ | emit .write(); | |
      | ^ ^ +--v---+ +---v---+
      | | ^-----------------------------------< No | | Yes |
      ^ | +------+ +---v---+
      ^ | |
      | ^ emit .pause(); +=================+ |
      | ^---------------^-----------------------+ return false; <-----+---+
      | +=================+ |
      | |
      ^ when queue is empty +============+ |
      ^------------^-----------------------< Buffering | |
      | |============| |
      +> emit .drain(); | ^Buffer^ | |
      +> emit .resume(); +------------+ |
      | ^Buffer^ | |
      +------------+ add chunk to queue |
      | <---^---------------------<
      +============+
    • 法则:

      • 如果没有需要,永远不要使用.push()
      • 永远不要在返回false后调用write,而是等待drain
  • 4、模块
    • 错误处理、安全性、消耗等问题
  • 5、How to publish N-API package 发布类似1.2.0-napi包