是什么?
把一个东西从 A 搬到 B 该怎么搬呢?
抬起来,移动到目的地,放下不就行了么。
那如果这个东西有一吨重呢?
那就一部分一部分的搬。
其实 IO 也就是搬东西,包括网络的 IO、文件的 IO,如果数据量少,那么直接传送全部内容就行了,但如果内容特别多,一次性加载到内存会崩溃,而且速度也慢,这时候就可以一部分一部分的处理,这就是流的思想。
nodejs 四种流
可写流,可读流,双工流,转换流, 下文只描述了 可写流和可读流 的使用。
流的直观感受
从一个地方流到另一个地方,显然有流出的一方和流入的一方,流出的一方就是可读流(readable),而流入的一方就是可写流(writable)。
背压
但是 read 和 write 都是异步的,如果两者速率不一致呢?
如果 Readable 读入数据的速率大于 Writable 写入速度的速率,这样就会积累一些数据在缓冲区,如果缓冲的数据过多,就会爆掉,会丢失数据。
而如果 Readable 读入数据的速率小于 Writable 写入速度的速率呢?那没关系,最多就是中间有段空闲时期。
这种读入速率大于写入速率的现象叫做“背压”
,或者“负压”
。也很好理解,写入段压力比较大,写不进去了,会爆缓冲区,导致数据丢失。
如何解决
当调用 writable stream 的 write 方法的时候会返回一个 boolean 值代表是写入了目标还是放在了缓冲区:
- true: 数据已经写入目标
- false:目标不可写入,暂时放在缓冲区
我们可以判断返回 false 的时候就 pause,然后等缓冲区清空了就 resume:
const rs = fs.createReadStream(srcFilename);
const ws = fs.createWriteStream(dstFilename);
//每当流将数据块的所有权移交给消费者时,则会触发 `'data'` 事件
rs.on('data', function (chunk) {
if (ws.write(chunk) === false) {
// 再写就背压了,暂停读取
rs.pause();
}
});
rs.on('end', function () {
ws.end();
});
//写入缓冲区清空时触发
ws.on('drain', function () {
//恢复读取流
rs.resume();
});
这样就能达到根据写入速率暂停和恢复读入速率的功能,解决了背压问题。
drain钩子官方解析
Event: 'drain'
#
Added in: v0.9.4
If a call to stream.write(chunk)
returns false
, the 'drain'
event will be emitted when it is appropriate to resume writing data to the stream.
也可以用pipe 管道
const rs = fs.createReadStream(src);
const ws = fs.createWriteStream(dst);
rs.pipe(ws);
pipe 内部已经做了读入速率的动态调节了。