在nodejs中从可写入的流中暂停管道可读流的正确方法是什么?

时间:2015-10-28 08:29:08

标签: node.js stream backpressure

我正在编写一个可写的流模块。我想为我的用户实现管道接口。

如果发生某些错误,我需要暂停可读流并发出错误事件。然后,用户将决定 - 如果他没有错误,他应该能够恢复数据处理。

var writeable = new BackPressureStream();
writeable.on('error', function(error){
    console.log(error);
    writeable.resume();
});

var readable = require('fs').createReadStream('somefile.txt');
readable.pipe.(writeable);

我看到该节点为我们提供了readable.pause()方法,可用于暂停可读流。但我无法从可写的流模块中调用它:

var Writable = require('stream').Writable;

function BackPressureStream(options) {
    Writable.call(this, options);
}
require('util').inherits(BackPressureStream, Writable);

BackPressureStream.prototype._write = function(chunk, encoding, done) {
    done();
};

BackPressureStream.prototype.resume = function() {
    this.emit('drain');
}

如何在可写流中实现背压?

P.S。可以使用提供可读流作为参数的pipe/unpipe事件。但也有人说,对于管道流,暂停的唯一机会是从可写入中删除可读流。

我做对了吗?我必须取消管理可写流,直到用户呼叫恢复?在用户调用恢复后,我应该管道可读流回来了吗?

3 个答案:

答案 0 :(得分:1)

无需访问源流或与源流交互。原生NodeJS流现在支持背压和缓冲。 pipe()负责两者。

您只需要正确实施_write()

function _write(chunk, enc, callback) {
    // if you don't invoke callback, data is buffered, and writes paused when buffer is full
}

引用文档:

  

在所有时间之间发生的对writable.write()的调用   调用writable._write()并调用回调将导致   要缓冲的书面数据。

转发错误后,在用户确认继续操作之前,不要为下一个chunk调用callback()。这将导致数据从源到缓冲区。

  

当writable.write(chunk)时,数据在Writable流中缓冲   方法被重复调用。而内部的总大小   写入缓冲区低于highWaterMark设置的阈值,调用   writable.write()将返回true。一旦内部的大小   缓冲区达到或超过highWaterMark,将返回false。

在可写流的缓冲区已满后,对write()的调用将返回false。如果源流实现表现良好或本机节点流,它将自动停止write()更多数据。

答案 1 :(得分:1)

您所描述的内容已经由pipe方法本身实现。来自文档的Errors While Writing部分:

  

如果Readable发出错误时,Writable流通过管道传输到Writable流中,则Readable流将被取消管道传输。

因此,作为可写流的实现者,您唯一的工作就是实现_write方法并在发生这种情况时发出错误。流模块将自动处理取消配管。然后,如果模块的使用者将错误视为非关键错误,则他们的工作就是将可读流通过管道传回。他们可以这样做:

var writeable = new BackPressureStream();
var readable = require('fs').createReadStream('somefile.txt');

writeable.on('error', function(error) {
    // use pipe again, if error is not critical
    if (!error.critical) {
        readable.pipe(writeable);
    } else {
        readable.destroy(error);
    }
});

readable.pipe(writeable);

在模块内部:

BackPressureStream.prototype._write = function(chunk, encoding, done) {
    // call done with an error to emit 'error' event and unpipe readable stream
    done(new Error('BOOM'));
};

答案 2 :(得分:0)

基本上,正如我所理解的那样,在发生错误事件时,您希望在流上加压。你有几个选择。

首先,正如您已经确定的那样,使用pipe来获取读取流的实例并做一些花哨的步法。

另一种选择是创建一个提供此功能的包装可写流(即它需要WritableStream作为输入,并且在实现流功能时,将数据传递给提供的流。

基本上你最终会得到像

这样的东西

source stream -> wrapping writable -> writable

https://nodejs.org/api/stream.html#stream_implementing_a_writable_stream涉及实现可写流。

关键是如果底层可写中发生错误,您将在流上设置一个标志,并且下一次调用write,您将缓冲该块,存储回调和只打电话。像

这样的东西
// ...
constructor(wrappedWritableStream) {
    wrappedWritableStream.on('error', this.errorHandler);
    this.wrappedWritableStream = wrappedWritableStream;
}
// ...
write(chunk, encoding, callback) {
    if (this.hadError) {
        // Note: until callback is called, this function won't be called again, so we will have maximum one stored
        //  chunk.
        this.bufferedChunk = [chunk, encoding, callback];
    } else {
        wrappedWritableStream.write(chunk, encoding, callback);
    }
}
// ...
errorHandler(err) {
    console.error(err);
    this.hadError = err;
    this.emit(err);
}
// ...
recoverFromError() {
    if (this.bufferedChunk) {
        wrappedWritableStream.write(...this.bufferedChunk);
        this.bufferedChunk = undefined;
    }
    this.hadError = false;
}

注意:您应该只需要实现write函数,但我鼓励您深入挖掘并使用其他实现函数。

值得注意的是,写入发生错误事件的流可能会遇到一些问题,但我会将此作为一个单独的问题解决。

这是另一个关于背压的好资源 https://nodejs.org/en/docs/guides/backpressuring-in-streams/