NodeJS Stream 理解
在unix中,我们可以使用|
符号来实现流,我们经常会在Shell中用到管道操作符。在Node中,流模块的基本操作符叫做.pipe()
。其中 Stream 涉及了操作系统中经典的生产者-消费者模型。
Stream 是 Node 移动数据的方式。Node JS有四种基本的Stream类型:
- Writable - 可写入数据的流(如 fs.createWriteStream())。
- Readable - 可读取数据的流(如 fs.createReadStream())。
- Duplex - 可读又可写的流(如 net.Socket)。
- Transform - 在读写过程中可以修改或转换数据的 Duplex 流(如 zlib.createDeflate())。
对于Stream,理解消费者-生产者模型即可明白原理,如何使用官方文档上也很清晰,在大部分业务场景中,我们使用.pipe()
方法来实现流的输入和输出即可,并由内部逻辑帮我们来处理读取和写入速度。
Stream 流使用对比
这里准备一个大小为29.5
MB的data.txt文件,进行性能测试。
我们可能会写出下面这种代码。在每次请求时,我们都会把整个data.txt文件读入到内存中,然后再把结果返回给客户端。
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
fs.readFile(__dirname + '/data.txt', (err, data) => {
res.end(data);
});
});
server.listen(8011);
使用ab -c 200 -t 100 http://127.0.0.1:8011/命令来进行性能测试,发起200个并发客户端。
上面代码由于每次请求时,会把整个data.txt文件读入到内存中,然后再把结果返回给客户端。并发过大,内存直接溢出,导致Node进程挂掉。
不过(req,res)
参数都是流对象,可以使用一种更好的方法来实现上面的需求:
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
const stream = fs.createReadStream(__dirname + '/data.txt');
stream.pipe(res);
});
server.listen(8011);
同样使用ab -c 200 -t 100 http://127.0.0.1:8001/测试,发起200个并发客户端。
在这里,.pipe()
方法会自动帮助我们处理读取速度,且将data.txt文件中每一小段数据将不断的发送到客户端,虽然QPS只有17.39,但是最大的保证了服务的可用性。
http服务中还有一个常见的需求,就是对数据进行gzip压缩,这里可以方便的使用流模块中的.pipe
来处理。
const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
const gzip = zlib.createGzip();
const server = http.createServer((req, res) => {
const stream = fs.createReadStream(__dirname + '/data.txt');
stream.pipe(gzip).pipe(res);
});
server.listen(8011);
首先读取到文件流,然后进过zlib流压缩,再将数据传输到TCP流中完成。
Stream模块让业务代码像连水管,极大的提高了我们的开发效率,当然性能也是杠杠的,所以以后能使用流的地方尽可能的使用。